Чтобы помочь моей команде в написании тестируемого кода, я составил этот простой список передовых методов, позволяющих сделать нашу базу кода C # более тестируемой. (Некоторые из пунктов относятся к ограничениям Rhino Mocks, фреймворка для имитации C #, но правила могут применяться и в более общем плане.) Есть ли у кого-нибудь какие-либо передовые методы, которым они следуют?
Чтобы максимизировать тестируемость кода, следуйте этим правилам:
Напишите сначала тест, а затем код. Причина: это гарантирует, что вы напишете тестируемый код и что для каждой строки кода будут написаны тесты.
Классы проектирования с использованием внедрения зависимостей. Причина: нельзя высмеивать или тестировать то, чего нельзя увидеть.
Отделите код пользовательского интерфейса от его поведения с помощью Model-View-Controller или Model-View-Presenter. Причина: позволяет тестировать бизнес-логику, в то время как части, которые не могут быть протестированы (пользовательский интерфейс), минимизированы.
Не пишите статические методы или классы. Причина: статические методы сложно или невозможно изолировать, и Rhino Mocks не может имитировать их.
Программируйте интерфейсы, а не классы. Причина: использование интерфейсов проясняет отношения между объектами. Интерфейс должен определять сервис, который требуется объекту из его среды. Кроме того, интерфейсы можно легко смоделировать с помощью Rhino Mocks и других фреймворков.
Изолируйте внешние зависимости. Причина: неразрешенные внешние зависимости не могут быть протестированы.
Отметьте как виртуальные методы, над которыми вы собираетесь издеваться. Причина: Rhino Mocks не может имитировать невиртуальные методы.
источник
Ответы:
Определенно хороший список. Вот несколько мыслей по этому поводу:
Согласен, на высоком уровне. Но я был бы более конкретным: «Сначала напишите тест, затем напишите достаточно кода, чтобы пройти тест, и повторите». В противном случае я бы побоялся, что мои модульные тесты будут больше похожи на интеграционные или приемочные тесты.
Согласовано. Когда объект создает свои собственные зависимости, вы не можете их контролировать. Инверсия управления / внедрения зависимостей дает вам этот контроль, позволяя изолировать тестируемый объект с помощью mocks / stubs / и т. Д. Вот как вы тестируете объекты изолированно.
Согласовано. Обратите внимание, что даже презентатор / контроллер можно протестировать с использованием DI / IoC, передав ему заглушку / имитацию представления и модели. Ознакомьтесь с Presenter First TDD, чтобы узнать больше об этом.
Не уверен, что согласен с этим. Можно провести модульное тестирование статического метода / класса без использования моков. Так что, возможно, это одно из тех особых правил Rhino Mock, о которых вы упомянули.
Я согласен, но по несколько другой причине. Интерфейсы предоставляют разработчику программного обеспечения большую гибкость - помимо поддержки различных фреймворков фиктивных объектов. Например, невозможно правильно поддерживать DI без интерфейсов.
Согласовано. Скрывайте внешние зависимости за собственным фасадом или адаптером (при необходимости) с помощью интерфейса. Это позволит вам изолировать ваше программное обеспечение от внешней зависимости, будь то веб-служба, очередь, база данных или что-то еще. Это особенно важно, когда ваша команда не контролирует зависимость (или внешнюю).
Это ограничение Rhino Mocks. В среде, которая предпочитает закодированные вручную заглушки над фреймворком имитирующих объектов, в этом нет необходимости.
И пара новых моментов, которые следует учитывать:
Используйте творческие шаблоны проектирования. Это поможет с DI, но также позволит изолировать этот код и протестировать его независимо от другой логики.
Напишите тесты, используя технику Билла Уэйка Arrange / Act / Assert . Этот метод дает очень четкое представление о том, какая конфигурация необходима, что на самом деле тестируется и что ожидается.
Не бойтесь накатывать собственные макеты / заглушки. Часто вы обнаружите, что использование фреймворков фиктивных объектов делает ваши тесты невероятно трудными для чтения. Создавая собственный, вы получите полный контроль над своими макетами / заглушками и сможете сохранить читабельность ваших тестов. (Вернитесь к предыдущему пункту.)
Избегайте соблазна реорганизовать дублирование ваших модульных тестов в абстрактные базовые классы или методы установки / удаления. Это скрывает код конфигурации / очистки от разработчика, пытающегося проверить модульный тест. В этом случае ясность каждого отдельного теста важнее, чем рефакторинг дублирования.
Реализуйте непрерывную интеграцию. Отметьте свой код на каждой «зеленой полосе». Создайте свое программное обеспечение и запускайте полный набор модульных тестов при каждой регистрации. (Конечно, это не практика программирования как таковая; но это невероятный инструмент для поддержания чистоты и полной интеграции вашего программного обеспечения.)
источник
Если вы работаете с .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));
источник
Знайте разницу между подделками, макетами и заглушками, и когда их использовать.
Избегайте чрезмерного определения взаимодействий с помощью имитаций. Это делает тесты хрупкими .
источник
Это очень полезный пост!
Я бы добавил, что всегда важно понимать контекст и тестируемую систему (SUT). Буквально следовать принципам TDD намного проще, когда вы пишете новый код в среде, где существующий код следует тем же принципам. Но когда вы пишете новый код в устаревшей среде без TDD, вы обнаруживаете, что ваши усилия по TDD могут быстро превзойти ваши оценки и ожидания.
Для некоторых из вас, кто живет в чисто академическом мире, сроки и доставка могут не иметь значения, но в среде, где программное обеспечение стоит денег, эффективное использование ваших усилий по TDD имеет решающее значение.
TDD строго подчиняется закону убывающей маржинальной прибыли . Короче говоря, ваши усилия в направлении TDD становятся все более ценными, пока вы не достигнете точки максимальной отдачи, после чего последующее время, вложенное в TDD, будет иметь все меньшую ценность.
Я склонен полагать, что основная ценность TDD находится в границах (черный ящик), а также в периодическом тестировании методом белого ящика критически важных областей системы.
источник
Настоящая причина программирования с использованием интерфейсов не в том, чтобы облегчить жизнь Rhino, а в том, чтобы прояснить отношения между объектами в коде. Интерфейс должен определять сервис, который требуется объекту из его среды. Класс предоставляет конкретную реализацию этой службы. Прочтите книгу Ребекки Вирфс-Брок «Дизайн объектов» о ролях, обязанностях и сотрудниках.
источник
Хороший список. Одна из вещей, которую вы, возможно, захотите установить - и я не могу дать вам много советов, так как сам только начинаю думать об этом - это когда класс должен находиться в другой библиотеке, пространстве имен, вложенных пространствах имен. Возможно, вы даже захотите заранее составить список библиотек и пространств имен и поручить команде встретиться и решить объединить две / добавить новую.
О, просто подумал о том, чем я занимаюсь, что ты тоже можешь захотеть. Как правило, у меня есть библиотека модульных тестов с набором инструментов для каждого класса, где каждый тест входит в соответствующее пространство имен. Я также предпочитаю иметь другую библиотеку тестов (интеграционные тесты?), Которая больше похожа на BDD . Это позволяет мне писать тесты, чтобы определить, что должен делать метод, а также то, что должно делать приложение в целом.
источник
Вот еще один, о котором я подумал, и который мне нравится делать.
Если вы планируете запускать тесты из Gui модульного тестирования, а не из TestDriven.Net или NAnt, то мне было проще установить тип проекта модульного тестирования на консольное приложение, а не на библиотеку. Это позволяет запускать тесты вручную и проходить их в режиме отладки (что на самом деле может сделать за вас вышеупомянутый TestDriven.Net).
Кроме того, мне всегда нравится иметь открытый проект Playground для тестирования фрагментов кода и идей, с которыми я не знаком. Это не следует проверять в системе контроля версий. Более того, он должен находиться в отдельном репозитории системы управления версиями только на машине разработчика.
источник