Модульное тестирование крайне тривиальных методов (да или нет) - PullRequest
8 голосов
/ 03 августа 2009

Предположим, у вас есть метод:

public void Save(Entity data)
{
    this.repositoryIocInstance.EntitySave(data);
}

Вы бы вообще написали юнит-тест?

public void TestSave()
{
    // arrange
    Mock<EntityRepository> repo = new Mock<EntityRepository>();
    repo.Setup(m => m.EntitySave(It.IsAny<Entity>());

    // act
    MyClass c = new MyClass(repo.Object);
    c.Save(new Entity());

    // assert
    repo.Verify(m => EntitySave(It.IsAny<Entity>()), Times.Once());
}

Потому что позже, если вы измените реализацию метода, сделайте более "сложные" вещи, такие как:

public void Save(Entity data)
{
    if (this.repositoryIocInstance.Exists(data))
    {
        this.repositoryIocInstance.Update(data);
    }
    else
    {
        this.repositoryIocInstance.Create(data);
    }
}

... ваш модульный тест не пройден, но, вероятно, он не сломает ваше приложение ...

Вопрос

Стоит ли вообще создавать модульные тесты для методов, которые не имеют типов возврата * или ** ничего не меняют вне внутреннего макета ?

Ответы [ 8 ]

7 голосов
/ 03 августа 2009

Не забывайте, что модульные тесты - это не просто тестирование кода. Это позволяет вам определять при изменении поведения .

Так что у вас может быть что-то тривиальное. Однако ваша реализация изменится, и у вас может возникнуть побочный эффект. Вы хотите, чтобы ваш набор регрессионных тестов сказал вам.

например. Часто люди говорят, что вы не должны тестировать сеттеры / геттеры, поскольку они тривиальны. Я не согласен, не потому что это сложные методы, но кто-то может непреднамеренно изменить их через невежество, сценарии с толстыми пальцами и т. Д.

Учитывая все, что я только что сказал, я бы определенно реализовал тесты для вышеперечисленного (с помощью насмешек, и / или, возможно, стоит разрабатывать ваши классы с учетом тестируемости и предоставления им отчета о состоянии и т.

3 голосов
/ 28 марта 2017

Задайте себе два вопроса. «Что такое ручной эквивалент этого модульного теста?» и "стоит ли автоматизировать?" В вашем случае это будет что-то вроде:

Что такое ручной эквивалент? - запустить отладчик - перейти в метод «Сохранить» - переходите к следующему, убедитесь, что вы находитесь внутри реализации IRepository.EntitySave

Стоит ли автоматизировать? Мой ответ - нет. Это на 100% очевидно из кода. Из сотен подобных тестов отходов я не увидел ни одного, который оказался бы полезным.

3 голосов
/ 03 августа 2009

Это правда, что ваш тест зависит от вашей реализации, которую вы должны избегать (хотя иногда это не так просто ...) и не обязательно плохой. Но ожидается, что такого рода тесты не пройдут, даже если ваши изменения не нарушат код .

Вы можете иметь много подходов к этому:

  • Создайте тест, который действительно отправляется в базу данных, и проверьте, изменилось ли состояние должным образом (оно не будет единицей тестом больше)
  • Создайте тестовый объект, который подделывает базу данных и выполняет операции в памяти (еще одна реализация для вашего repositoryIocInstance), и убедитесь, что состояние было изменено, как и ожидалось. Изменения в интерфейсе репозитория также повлекут за собой изменения в этом объекте. Но ваши интерфейсы не должны сильно меняться, верно?
  • Считайте, что все это слишком дорого, и используйте ваш подход, который может повлечь за собой ненужное прерывание тестов позже (но если вероятность невелика, то рискнуть будет нормально)
2 голосов
/ 03 августа 2009

Короткий ответ на ваш вопрос: Да , вы обязательно должны проверить такие методы.

Я предполагаю, что важно , что метод Save на самом деле сохраняет данные. Если вы не пишете для этого модульный тест, то откуда вы знаете?

Кто-то другой может прийти и удалить эту строку кода, которая вызывает метод EntitySave, и ни один из модульных тестов не пройдет. Позже вы задаетесь вопросом, почему предметы никогда не сохраняются ...

В вашем методе вы могли бы сказать, что любой, удаляющий эту строку, будет делать это только в том случае, если у него есть злонамеренные намерения, но дело в том, что простые вещи не обязательно остаются простыми, и вам лучше написать модульные тесты, прежде чем что-то получится. сложнее.

Это , а не деталь реализации, когда метод Save вызывает EntitySave в репозитории - это часть ожидаемого поведения и довольно важная часть, если можно так выразиться. Вы хотите убедиться, что данные действительно сохраняются.

Тот факт, что метод не возвращает значение, не означает, что его не стоит проверять. В общем, если вы наблюдаете хорошее разделение команд / запросов (CQS), любой метод void должен изменить состояние что-то .

Иногда это что-то является самим классом, но иногда это может быть состояние чего-то другого. В этом случае он меняет состояние репозитория, и это то, что вы должны тестировать.

Это называется тестированием Независимых выходов вместо более обычных Прямых выходов (возвращаемые значения).

Хитрость в том, чтобы писать модульные тесты, чтобы они не ломались слишком часто. При использовании Mocks можно легко написать Overspecified Tests , поэтому большинство динамических Mocks (например, Moq) по умолчанию используют режим Stub , где на самом деле не имеет значения, сколько раз Вы вызываете данный метод.

Все это и многое другое объясняется в превосходных xUnit Test Patterns .

2 голосов
/ 03 августа 2009

Общее правило гласит: проверяйте все, что может сломаться. Если вы уверены, что метод достаточно прост (и остается достаточно простым), чтобы не создавать проблем, выпустите его с тестированием.

Во-вторых, вы должны проверять контракт метода, а не реализацию. Если тест не пройден после изменения, но не приложения, тогда ваш тест не подходит. Тест должен охватывать случаи, которые важны для вашего приложения. Это должно гарантировать, что каждое изменение в методе, которое не нарушает работу приложения, также не проходит тест.

1 голос
/ 03 августа 2009

Если в методе нет утверждения, вы, по сути, утверждаете, что исключения не выбрасываются.

Я также борюсь с вопросом о том, как проверить public void myMethod (). Я полагаю, если вы решите добавить возвращаемое значение для тестируемости, возвращаемое значение должно отражать все существенные факты, необходимые для просмотра изменений в состоянии приложения.

public void myMethod()

становится

 public ComplexObject myMethod() { 
 DoLotsOfSideEffects()
 return new ComplexObject { rows changed, primary key, value of each column, etc };
 }

а не

public bool myMethod()  
  DoLotsOfSideEffects()
  return true;
1 голос
/ 03 августа 2009

"ваш модульный тест не пройдёт, но, вероятно, он не сломает ваше приложение"

Это - на самом деле - очень важно знать. Это может показаться раздражающим и тривиальным, но когда кто-то еще начинает поддерживать ваш код, он, возможно, сделал действительно плохое изменение, чтобы сохранить и (невероятно) сломать приложение.

Хитрость в том, чтобы расставить приоритеты.

Сначала проверьте важные вещи. Когда дела идут медленно, добавьте тесты на тривиальные вещи.

1 голос
/ 03 августа 2009

Метод, который не возвращает никакого результата, все равно изменяет состояние вашего приложения. В этом случае ваш модульный тест должен проверять, соответствует ли новое состояние предполагаемому.

...