Лучшие практики разработки через тестирование с использованием 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 не может имитировать невиртуальные методы.

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

Ответы:

58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

гусеница
источник
3
Обычно я считаю, что если тест трудно читать, то это вина не фреймворка, а кода, который он тестирует. Если SUT сложно настроить, то, возможно, следует разбить его на несколько концепций.
Стив Фриман,
10

Если вы работаете с .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));
задача
источник
5
Думаю, новая версия Rhino Mocks тоже работает так же,
Джордж Мауэр,
3

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

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

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

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

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


источник
2

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

Стив Фриман
источник
Согласен ... Я обновлю свой вопрос, чтобы отразить это.
Кевин Альбрехт,
1

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

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

Джордж Мауэр
источник
Я также провожу аналогичный раздел тестирования стиля BDD (в дополнение к коду модульного тестирования) в личном проекте.
Кевин Альбрехт,
0

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

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

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

Джордж Мауэр
источник