Лучшие практики разработки через тестирование с использованием C # и RhinoMocks

Чтобы помочь моей команде писать тестируемый код, я составил этот простой список лучших практик, чтобы сделать нашу кодовую базу C# более тестируемой. (Некоторые пункты относятся к ограничениям Rhino Mocks, фиктивной среды для C#, но правила могут применяться и в более общем плане.) Есть ли у кого-нибудь передовой опыт, которому они следуют?

Чтобы максимизировать тестируемость кода, следуйте этим правилам:

  1. Сначала напишите тест, а затем код. Причина. Это гарантирует, что вы пишете тестируемый код и что для каждой строки кода написаны тесты.

  2. Создавайте классы, используя внедрение зависимостей. Причина: нельзя имитировать или тестировать то, что невидимо.

  3. Отделите код пользовательского интерфейса от его поведения с помощью Model-View-Controller или Model-View-Presenter. Причина: позволяет тестировать бизнес-логику, в то время как части, которые нельзя протестировать (пользовательский интерфейс), свернуты. .

  4. Не пишите статические методы или классы. Причина. Статические методы трудно или невозможно изолировать, а Rhino Mocks не может их имитировать.

  5. Программируйте интерфейсы, а не классы. Причина: использование интерфейсов проясняет отношения между объектами. Интерфейс должен определять службу, которая нужна объекту из его среды. Кроме того, интерфейсы можно легко имитировать с помощью Rhino Mocks и других фреймворков.

  6. Изолировать внешние зависимости. Причина: невозможно протестировать неразрешенные внешние зависимости.

  7. Отметить как виртуальные методы, которые вы собираетесь имитировать. Причина: Rhino Mocks не может имитировать невиртуальные методы.


person Kevin Albrecht    schedule 23.09.2008    source источник
comment
Это полезный список. В настоящее время мы используем NUnit и Rhino.Mocks, и было бы хорошо разъяснить эти критерии членам команды, которые менее знакомы с этой стороной модульного тестирования.   -  person Chris Ballard    schedule 24.09.2008


Ответы (7)


Определенно хороший список. Вот несколько мыслей по этому поводу:

Сначала напишите тест, а затем код.

Согласен, на высоком уровне. Но я бы сказал более конкретно: «Сначала напишите тест, затем напишите достаточно кода, чтобы пройти тест, и повторите». В противном случае я бы боялся, что мои модульные тесты будут больше похожи на интеграционные или приемочные тесты.

Создавайте классы с помощью внедрения зависимостей.

Согласованный. Когда объект создает свои собственные зависимости, вы не можете их контролировать. Инверсия управления/внедрение зависимостей дает вам этот контроль, позволяя изолировать тестируемый объект с помощью mocks/stubs/etc. Так вы тестируете объекты изолированно.

Отделите код пользовательского интерфейса от его поведения с помощью Model-View-Controller или Model-View-Presenter.

Согласованный. Обратите внимание, что даже презентатор/контроллер можно протестировать с помощью DI/IoC, передав ему заглушенное/издевательское представление и модель. Чтобы узнать больше об этом, посетите Presenter First TDD.

Не пишите статические методы или классы.

Не уверен, что согласен с этим. Можно провести модульное тестирование статического метода/класса без использования моков. Итак, возможно, это одно из тех конкретных правил Rhino Mock, которые вы упомянули.

Программируйте интерфейсы, а не классы.

Я согласен, но немного по другой причине. Интерфейсы обеспечивают большую гибкость для разработчика программного обеспечения — помимо поддержки различных фреймворков фиктивных объектов. Например, невозможно правильно поддерживать DI без интерфейсов.

Изолируйте внешние зависимости.

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

Отметьте как виртуальные методы, над которыми вы хотите посмеяться.

Это ограничение Rhino Mocks. В среде, которая предпочитает написанные вручную заглушки фреймворку фиктивных объектов, в этом нет необходимости.

И пара новых моментов для рассмотрения:

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

Напишите тесты, используя метод Arrange/Act/Assert Билла Уэйка. Этот метод очень четко показывает, какая конфигурация необходима, что на самом деле тестируется и что ожидается.

Не бойтесь создавать собственные макеты/заглушки. Часто вы обнаружите, что использование фреймворков макетов объектов делает ваши тесты невероятно сложными для чтения. Свернув свои собственные, вы получите полный контроль над своими макетами/заглушками и сможете сделать свои тесты читабельными. (Вернитесь к предыдущему пункту.)

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

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

person aridlehoover    schedule 24.09.2008
comment
Обычно я считаю, что если тест трудно читать, это не вина фреймворка, а кода, который он тестирует. Если SUT сложна в настройке, то, возможно, ее следует разбить на несколько концепций. - person Steve Freeman; 06.09.2009

Если вы работаете с .Net 3.5, вы можете заглянуть в библиотеку имитации Moq — она использует деревья выражений и лямбда-выражения для удаления неинтуитивной идиомы записи-ответа большинства других фиктивных библиотек.

Ознакомьтесь с этим кратким руководством, чтобы увидеть, насколько интуитивно понятными становятся ваши тестовые сценарии. простой пример:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));
person zadam    schedule 24.09.2008
comment
Думаю, новая версия Rhino Mocks тоже так работает. - person George Mauer; 24.09.2008

Знайте разницу между подделками, макетами и заглушками и когда их использовать.

Избегайте чрезмерного указания взаимодействий с помощью моков. Это делает тесты хрупкими.

person Hamish Smith    schedule 24.09.2008

Это очень полезный пост!

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

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

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

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

person Community    schedule 06.05.2009

Настоящая причина программирования против интерфейсов не в том, чтобы облегчить жизнь Rhino, а в том, чтобы прояснить отношения между объектами в коде. Интерфейс должен определять службу, которая нужна объекту из его среды. Класс предоставляет конкретную реализацию этой службы. Прочтите книгу Ребекки Вирфс-Брок «Объектный дизайн» о ролях, обязанностях и сотрудниках.

person Steve Freeman    schedule 06.09.2009
comment
Согласен ... Я собираюсь обновить свой вопрос, чтобы отразить это. - person Kevin Albrecht; 10.09.2009

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

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

person George Mauer    schedule 24.09.2008
comment
Я также делаю аналогичный раздел тестирования в стиле BDD (в дополнение к коду модульного тестирования) в личном проекте. - person Kevin Albrecht; 24.09.2008

Вот еще один, о котором я подумал, что мне нравится делать.

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

Кроме того, мне всегда нравится иметь открытый проект Playground для тестирования кусков кода и идей, с которыми я не знаком. Это не должно быть проверено в системе контроля версий. Более того, он должен находиться в отдельном репозитории системы управления версиями только на компьютере разработчика.

person George Mauer    schedule 24.09.2008