Подтвердить что ты не робот

Советы по написанию рефакторинговых тестов TDD

Я работаю над проектом ASP.NET MVC около 8 месяцев. По большей части я использую TDD, некоторые аспекты были рассмотрены модульными тестами только после того, как я написал фактический код. В целом проект довольно хороший тест покрытия.

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

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

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

Кроме того, есть ли у вас дополнительные советы о том, как писать тесты TDD, чтобы рефакторинг прерывал как можно меньше тестов?

Изменить: Как правильно заметил Хеннинг и тванфоссон, обычно это часть настройки, которая наиболее дорога для записи и обслуживания. Сломанные тесты (по моему опыту) обычно являются результатом рефакторинга модели домена, которая несовместима с частью установки этих тестов.

4b9b3361

Ответ 1

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

После длительного использования этих шаблонов я написал AutoFixture, который является библиотекой с открытым исходным кодом, которая инкапсулирует многие из этих основных узоры.

Он работает как Test Data Builder, но также может быть подключен для работы в качестве контейнера Auto-Mocking и делает много других странных и замечательные вещи.

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

Представьте, что у вас есть класс с этой подписью конструктора

public MyClass(Foo foo, Bar bar, Sgryt sgryt)

Пока AutoFixture может разрешить все аргументы конструктора, вы можете просто создать новый экземпляр, например:

var sut = fixture.CreateAnonymous<MyClass>();

Главное преимущество заключается в том, что если вы решите реорганизовать конструктор MyClass, тесты не будут разбиты, потому что AutoFixture поможет вам в этом.

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

Ответ 2

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

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

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

Ответ 3

Эта статья мне очень помогла: http://msdn.microsoft.com/en-us/magazine/cc163665.aspx

С другой стороны, нет метода чуда, чтобы избежать рефакторинговых модульных тестов.

Все идет с ценой, и это особенно актуально, если вы хотите провести модульное тестирование.

Ответ 4

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

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

Однако это не очень хорошо, потому что нам все равно нужно создавать все эти значения в каждом тесте. Итак, мы сейчас изучаем наши тесты в стиле спецификации /BDD, что должно помочь уменьшить код установки и, следовательно, время, затрачиваемое на поддержание тестов. Несколько ресурсов, которые вы можете проверить, http://elegantcode.com/2009/12/22/specifications/ и стиль тестирования BDD с помощью MSpec http://elegantcode.com/2009/07/05/mspec-take-2/

Ответ 5

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

Ответ 6

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

Стоит отметить, что я стараюсь держаться подальше от BDD/context spec дальше от пользовательского интерфейса, который я получаю. Тестирование поведения замечательно, но всегда приводит меня (возможно, я не делаю это правильно?) К более крупным тестовым тестам с большей спецификацией контекста, чем мне нравится.

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

Ответ 7

Если вы обнаружите, что создаете сложные тестовые леса с использованием глубоких графиков объектов, таких как русские куклы, рассмотрите возможность рефакторинга вашего кода, чтобы получить тест класса Under именно то, что ему нужно в его конструкторе/аргументах, а не в том, чтобы ходить по графику.

:

public class A {

   public void foo(B b) {
      String someField = b.getC().getD().getSomeField();
      // ...
   }
} 

Измените его на:

public class A {

   public void foo(String someField) {
      // ...
   }
} 

Затем ваша тестовая установка становится тривиальной.