2
\$\begingroup\$

Which unit testing approach and WHY do you prefer?

Inheritance-driven-testing

public class GivenSynchronizedDataLink : TestBase
{
    internal SynchronizedDataLink Subject { get; private set; }

    protected Mock<IDataLink> DataLinkFake { get; private set; }
    protected Mock<IAutoReaderWriterLock> ReaderWriterFake { get; private set; }

    private Fixture fixture;

    protected override void Setup()
    {
        fixture = new Fixture();
        ReaderWriterFake = new Mock<IAutoReaderWriterLock>();
        DataLinkFake = new Mock<IDataLink>();
    }

    protected override void Given()
    {
        Subject = new SynchronizedDataLink(DataLinkFake.Object, () => ReaderWriterFake.Object);
    }

    protected T Any<T>()
    {
        return fixture.Create<T>();
    }
}

[TestClass]
public class WhenDisposeCalled : GivenSynchronizedDataLink
{
    [TestMethod]
    public void ThenAquiresWriteLock()
    {
        ReaderWriterFake.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void When()
    {
        Subject.Dispose();
    }
}

[TestClass]
public class WhenDisposeCalledTwice : GivenSynchronizedDataLink
{
    [TestMethod]
    public void ThenAquiresWriteLockOnlyOnce()
    {
        ReaderWriterFake.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void When()
    {
        Subject.Dispose();
        Subject.Dispose();
    }
}

[TestClass]
public class WhenGetMinStampGreaterThanCalled : GivenSynchronizedDataLink
{
    private Mock<IDisposable> lockRealeaserMock;
    private DateTime stamp;

    [TestMethod]
    public void ThenAquiresReadLock()
    {
        ReaderWriterFake.Verify(x => x.AcquireReadLock(), Times.Once());
    }

    [TestMethod]
    public void ThenInvokesDataLink()
    {
        DataLinkFake.Verify(x => x.GetMinStampGreaterThan(stamp), Times.Once());
    }

    [TestMethod]
    public void ThenReleasesReadLock()
    {
        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void Setup()
    {
        base.Setup();

        lockRealeaserMock = new Mock<IDisposable>();
        ReaderWriterFake.Setup(x => x.AcquireReadLock())
            .Returns(lockRealeaserMock.Object);
    }

    protected override void Given()
    {
        base.Given();

        stamp = Any<DateTime>();
    }

    protected override void When()
    {
        Subject.GetMinStampGreaterThan(stamp);
    }
}

[TestClass]
public class WhenRemoveValuesWithArrayCalled : GivenSynchronizedDataLink
{
    private Mock<IDisposable> lockRealeaserMock;
    private ERemoveOptions options;
    private DateTime[] stamps;

    [TestMethod]
    public void ThenAquiresWriteLock()
    {
        ReaderWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void ThenInvokesDataLink()
    {
        DataLinkFake.Verify(x => x.RemoveValues(stamps, options), Times.Once());
    }

    [TestMethod]
    public void ThenReleasesWriteLock()
    {
        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void Setup()
    {
        base.Setup();

        lockRealeaserMock = new Mock<IDisposable>();
        ReaderWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);
    }

    protected override void Given()
    {
        base.Given();

        stamps = Any<DateTime[]>();
        options = Any<ERemoveOptions>();
    }

    protected override void When()
    {
        Subject.RemoveValues(stamps, options);
    }
}

[TestClass]
public class WhenRemoveValuesWithIntervalCalled : GivenSynchronizedDataLink
{
    private DateTime fromIncl;
    private Mock<IDisposable> lockRealeaserMock;
    private ERemoveOptions options;
    private DateTime tillIncl;

    [TestMethod]
    public void ThenAquiresWriteLock()
    {
        ReaderWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void ThenInvokesDataLink()
    {
        DataLinkFake.Verify(x => x.RemoveValues(fromIncl, tillIncl, options), Times.Once());
    }

    [TestMethod]
    public void ThenReleasesWriteLock()
    {
        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void Setup()
    {
        base.Setup();

        lockRealeaserMock = new Mock<IDisposable>();
        ReaderWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);
    }

    protected override void Given()
    {
        base.Given();

        fromIncl = Any<DateTime>();
        tillIncl = Any<DateTime>();
        options = Any<ERemoveOptions>();
    }

    protected override void When()
    {
        Subject.RemoveValues(fromIncl, tillIncl, options);
    }
}

[TestClass]
public class WhenSaveValuesCalled : GivenSynchronizedDataLink
{
    private Mock<IDisposable> lockRealeaserMock;
    private UniqueDescendingValueCollection values;

    [TestMethod]
    public void ThenAquiresWriteLock()
    {
        ReaderWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void ThenInvokesDataLink()
    {
        DataLinkFake.Verify(x => x.SaveValues(values), Times.Once());
    }

    [TestMethod]
    public void ThenReleasesWriteLock()
    {
        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    protected override void Setup()
    {
        base.Setup();

        lockRealeaserMock = new Mock<IDisposable>();
        ReaderWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);
    }

    protected override void When()
    {
        values = AnyValues();
        Subject.SaveValues(values);
    }

    private UniqueDescendingValueCollection AnyValues()
    {
        return new UniqueDescendingValueCollection(new IValue[0]);
    }
}

Single class

[TestClass]
public class SynchronizedDataLinkTests
{
    private readonly SynchronizedDataLink cut;
    private readonly Mock<IDataLink> dataLinkFake;
    private readonly Fixture fixture;
    private readonly Mock<IAutoReaderWriterLock> readerWriterFake;

    public SynchronizedDataLinkTests()
    {
        fixture = new Fixture();
        readerWriterFake = new Mock<IAutoReaderWriterLock>();
        dataLinkFake = new Mock<IDataLink>();
        cut = new SynchronizedDataLink(dataLinkFake.Object, () => readerWriterFake.Object);
    }

    [TestMethod]
    public void Dispose_DisposesLock()
    {
        cut.Dispose();

        readerWriterFake.Verify(x => x.Dispose(), Times.Once());
    }

    [TestMethod]
    public void GetMinStampGreaterThan_AquiresReadLock()
    {
        ExecuteGetMinStampGreaterThan();

        readerWriterFake.Verify(x => x.AcquireReadLock(), Times.Once());
    }

    [TestMethod]
    public void GetMinStampGreaterThan_InvokesDataLink()
    {
        DateTime stamp = ExecuteGetMinStampGreaterThan();

        dataLinkFake.Verify(x => x.GetMinStampGreaterThan(stamp), Times.Once());
    }

    [TestMethod]
    public void GetMinStampGreaterThan_ReleasesReadLock()
    {
        var lockRealeaserMock = new Mock<IDisposable>();
        readerWriterFake.Setup(x => x.AcquireReadLock())
            .Returns(lockRealeaserMock.Object);

        ExecuteGetMinStampGreaterThan();

        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithArray_AquiresWriteLock()
    {
        ExecuteRemoveValuesWithArray();

        readerWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithArray_InvokesDataLink()
    {
        Tuple<DateTime[], ERemoveOptions> input = ExecuteRemoveValuesWithArray();

        dataLinkFake.Verify(x => x.RemoveValues(input.Item1, input.Item2), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithArray_ReleasesWriteLock()
    {
        var lockRealeaserMock = new Mock<IDisposable>();
        readerWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);

        ExecuteRemoveValuesWithArray();

        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithInterval_AquiresWriteLock()
    {
        ExecuteRemoveValuesWithInterval();

        readerWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithInterval_InvokesDataLink()
    {
        Tuple<DateTime, DateTime, ERemoveOptions> input = ExecuteRemoveValuesWithInterval();

        dataLinkFake.Verify(x => x.RemoveValues(input.Item1, input.Item2, input.Item3), Times.Once());
    }

    [TestMethod]
    public void RemoveValuesWithInterval_ReleasesWriteLock()
    {
        var lockRealeaserMock = new Mock<IDisposable>();
        readerWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);

        ExecuteRemoveValuesWithInterval();

        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    [TestMethod]
    public void SaveValues_AquiresWriteLock()
    {
        ExecuteSaveValues();

        readerWriterFake.Verify(x => x.AcquireWriteLock(), Times.Once());
    }

    [TestMethod]
    public void SaveValues_InvokesDataLink()
    {
        UniqueDescendingValueCollection values = ExecuteSaveValues();

        dataLinkFake.Verify(x => x.SaveValues(values), Times.Once());
    }

    [TestMethod]
    public void SaveValues_ReleasesWriteLock()
    {
        var lockRealeaserMock = new Mock<IDisposable>();
        readerWriterFake.Setup(x => x.AcquireWriteLock())
            .Returns(lockRealeaserMock.Object);

        ExecuteSaveValues();

        lockRealeaserMock.Verify(x => x.Dispose(), Times.Once());
    }

    private T Any<T>()
    {
        return fixture.Create<T>();
    }

    private UniqueDescendingValueCollection AnyValues()
    {
        return new UniqueDescendingValueCollection(new IValue[0]);
    }

    private DateTime ExecuteGetMinStampGreaterThan()
    {
        var stamp = Any<DateTime>();

        cut.GetMinStampGreaterThan(stamp);
        return stamp;
    }

    private Tuple<DateTime[], ERemoveOptions> ExecuteRemoveValuesWithArray()
    {
        var stamp = Any<DateTime[]>();
        var options = Any<ERemoveOptions>();

        cut.RemoveValues(stamp, options);
        return new Tuple<DateTime[], ERemoveOptions>(stamp, options);
    }

    private Tuple<DateTime, DateTime, ERemoveOptions> ExecuteRemoveValuesWithInterval()
    {
        var from = Any<DateTime>();
        var till = Any<DateTime>();
        var options = Any<ERemoveOptions>();

        cut.RemoveValues(from, till, options);
        return new Tuple<DateTime, DateTime, ERemoveOptions>(from, till, options);
    }

    private UniqueDescendingValueCollection ExecuteSaveValues()
    {
        UniqueDescendingValueCollection values = AnyValues();

        cut.SaveValues(values);
        return values;
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ Your question is asking for an opinion. Could you rephrase it to be less opinionated (is this a real word ) ? You could chose one of those approach and see the review of it. You could always make a second question for the second and see which one looks best, but it should not be in the same question IMO. \$\endgroup\$
    – Marc-Andre
    Mar 20, 2014 at 14:05
  • 1
    \$\begingroup\$ I personally prefer the second, but perhaps with more TestPropertyAttributes if you want to be able to find them quickly. \$\endgroup\$
    – Magus
    Mar 20, 2014 at 14:44
  • \$\begingroup\$ I want to know which implementation is more readable and maintainable. Isn't it a part of test code review? \$\endgroup\$
    – Pellared
    Mar 20, 2014 at 21:29

1 Answer 1

2
\$\begingroup\$

I'd favor the second one, it seems that the first one abusing inheritance (uses it for only code reuse) and I think it's harder to read because you have to go back and forth to the parent class.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.