Рекомендации по разработке тестов с использованием C# и RhinoMocks


86

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

Для максимального тестируемости кода, соблюдайте следующие правила:

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

  2. Дизайн классов с использованием инъекции зависимости. Причина: Вы не можете издеваться или тестировать то, что не видно.

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

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

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

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

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

  0

Это полезный список. В настоящее время мы используем NUnit и Rhino.Mocks, и хорошо изложить эти критерии для членов команды, которые менее знакомы с этой стороной модульного тестирования. 24 сен. 082008-09-24 13:38:02

56

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

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

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

Дизайн классов с использованием инъекции зависимости.

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

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

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

Не записывайте статические методы или классы.

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

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

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

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

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

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

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

И несколько новых точек, чтобы рассмотреть следующие вопросы:

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

Напишите тесты, используя Bill Wake's Arrange/Act/Assert technique. Этот метод очень четко показывает, какая конфигурация необходима, что на самом деле тестируется и что ожидается.

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

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

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

+2

Обычно я обнаружил, что если тест трудно читать, это не ошибка структуры, а кода, который он тестирует. Если SUT сложно настроить, то, возможно, он должен быть разбит на более понятные понятия. 06 сен. 092009-09-06 19:42:58


1

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

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

  0

Я также делаю аналогичный раздел тестирования стиля BDD (помимо кода модульного тестирования) в личном проекте. 24 сен. 082008-09-24 00:11:03


6

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

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


10

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

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

// 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)); 
+5

Я думаю, что новая версия Rhino Mocks работает так же 24 сен. 082008-09-24 13:21:06


0

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

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

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


3

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

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

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

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

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


2

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

  0

Согласен ... Я собираюсь обновить свой вопрос, чтобы это отразить. 09 сен. 092009-09-09 23:29:56