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

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

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

Классы проектирования с использованием инъекции зависимостей.

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

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

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

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

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

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

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

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

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

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

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

И еще пару новых моментов:

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

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

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

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

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

Ответ 2

Если вы работаете с .Net 3.5, вам может понадобиться изучить Moq mocking library - она ​​использует деревья выражений и 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));

Ответ 3

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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