TDD с C#, проблема с написанием тестов с макетом - PullRequest
1 голос
/ 17 марта 2020

Я делаю первые шаги в TDD (я изучаю «Профессиональную разработку через тестирование с C#» Бендера и МакВертера).

Я пытаюсь написать свое первое приложение с TDD: я предполагаю, что у меня есть класс DataService для управления сохранением. Я написал два пройденных теста, но думаю, что у меня нет смысла.

Это мой первый тест, я предполагаю, что могу создать экземпляр транзакции, что бы это ни значило в DataService

public void Begin_a_transaction_should_returns_true_when_is_all_ok()
{
    Mock<IDataLayer> dataLayer = new Mock<IDataLayer>();
    DataService sut = new DataService(dataLayer.Object);

    bool expected = true;
    dataLayer.Setup(dl => dl.BeginTransaction()).Returns(expected);

    bool actual = sut.BeginTransaction();

    Assert.AreEqual(expected, actual);
}

В соответствии с TDD теперь я написал классы, никаких проблем с этим

public class DataService
{
    private IDataLayer _dataLayer;

    public DataService(IDataLayer dataLayer)
    {
        this._dataLayer = dataLayer;
    }

    public bool BeginTransaction()
    {
        return _dataLayer.BeginTransaction();
    }
}

Теперь я хочу написать второй тест: BeginTransaction должен завершиться неудачей, если транзакция уже существует, и я требую это для IDataLayer

[Test]
public void Begin_a_transaction_should_throws_exception_if_transaction_already_exists()
{
    Mock<IDataLayer> dataLayer = new Mock<IDataLayer>();
    DataService sut = new DataService(dataLayer.Object);

    dataLayer.Setup(dl => dl.BeginTransaction()).Throws(new Exception("Transaction already exists"));

    Assert.Throws<Exception>(() => sut.BeginTransaction());
}

А теперь суть: тесты проходят без написания какой-либо строки кода, потому что я издевался над BeginTransaction, чтобы вызвать исключение. Это может быть хорошо, потому что я протестирую это в реализации тестов IDataLayer, но если я все высмеиваю, тесты DataService полезны?

Ответы [ 2 ]

1 голос
/ 17 марта 2020

Я бы сказал, что это происходит потому, что вы тестируете поведение класса, который не имеет никакого поведения, кроме переноса IDataLayer - все, что происходит в переносимом классе, просто передается наружу. То же самое, что вы описываете, происходит в случае, когда метод возвращает true, хотя это менее очевидно.

Чтобы сделать ваш пример более значимым, вы можете добавить некоторое поведение, которое зависит от результата IDataLayer's BeginTransaction(), вроде;

Реализация

public class DataService
{
    public DataService(IDataLayer dataLayer, IBookRepository bookRepository) 
    {
        _dataLayer = dataLayer;
        _bookRepository = bookRepository;
    }

    public void StoreBookInfo(string data)
    {
        if (_dataLayer.BeginTransaction())
            _bookRepository.StoreBookInfo(data);
        else
            throw new SomeException();
    }
}

Тест

[Test]
public void Should_store_book_info_if_transaction_can_be_started()
{
    Mock<IDataLayer> dataLayer = new Mock<IDataLayer>();
    Mock<IBookRepository> bookRepository = new Mock<IBookRepository>();

    dataLayer.Setup(dl => dl.BeginTransaction()).Returns(true);
    bookRepository.Setup(x => x.StoreBookInfo(It.IsAny<string>()));

    DataService sut = new DataService(dataLayer.Object, bookRepository.Object);

    sut.StoreBookInfo("SomeValue");

    bookRepository.Verify(x => x.StoreBookInfo(It.IsAny<string>()));
}

[Test]
public void Should_throw_exception_if_transaction_cannot_be_started()
{
    Mock<IDataLayer> dataLayer = new Mock<IDataLayer>();
    Mock<IBookRepository> bookRepository = new Mock<IBookRepository>();

    dataLayer.Setup(dl => dl.BeginTransaction()).Returns(false);

    DataService sut = new DataService(dataLayer.Object, bookRepository.Object);

    Assert.Throws<SomeException>(() => sut.StoreBookInfo("someValue"));
}

0 голосов
/ 17 марта 2020

Что ж, я пересмотрел свой код следующим образом.

public class DataService
    {
        private IDataLayer _dataLayer;
        protected object _transaction;

        public DataService(IDataLayer dataLayer)
        {
            this._dataLayer = dataLayer;
        }

        public bool BeginTransaction()
        {
            if (_transaction != null)
                throw new Exception("Transaction already exists");

            _transaction = _dataLayer.BeginTransaction();

            return true;
        }
    }

Чем я использую шаблон декоратора в классе теста

internal class DataServiceDecorator : DataService
        {
            public DataServiceDecorator(IDataLayer dataLayer) : base(dataLayer)
            { }

            public void InjectTransactionObject(object transaction)
            {
                this._transaction = transaction;
            }
        }

Так что я могу написать этот тест

[Test]
        public void Begin_a_transaction_should_throws_exception_if_transaction_already_exists()
        {
            Mock<IDataLayer> dataLayer = new Mock<IDataLayer>();
            DataServiceDecorator sut = new DataServiceDecorator(dataLayer.Object);

            sut.InjectTransactionObject(new object());

            Assert.Throws<Exception>(() => sut.BeginTransaction());
        }

Что вы думаете об этом?

...