Какие объекты высмеивать при выполнении TDD - PullRequest
9 голосов
/ 02 ноября 2009

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

У нас здесь много методов, которые не имеют связанных юнит-тестов и ретроспективно пишущих тесты; мы находим, что внутри этих методов создается довольно много объектов.

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

Что ты думаешь? Должны ли все объекты, созданные в методе, передаваться как параметры?

Ответы [ 10 ]

7 голосов
/ 02 ноября 2009

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

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

Допустим, ваша первоначальная реализация выглядела так:

public class Foo
{
    public Ploeh DoStuff(Fnaah f)
    {
        var bar = new Bar();
        return bar.DoIt(f);
    }
}

Это можно изменить, чтобы оно выглядело так:

public class Foo
{
    private readonly IBar bar;

    public Foo(IBar bar)
    {
        this.bar = bar;
    }

    public Ploeh DoStuff(Fnaah f)
    {
        return this.bar.DoIt(f);
    }
}

Обратите внимание, что я изменил bar с экземпляра Bar на экземпляр IBar, таким образом отделяя Foo от конкретной реализации IBar. Такой рефакторинг, как правило, упрощает ваши модульные тесты для написания и поддержки , поскольку теперь вы можете независимо изменять реализации Foo и Bar.

6 голосов
/ 02 ноября 2009

Первая часть - немного загруженный вопрос.Это все равно, что спросить: «Когда я бегу по пешеходам в моей машине, должен ли я держать обе руки на руле?»

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

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

Итак, краткий ответ: «Да», передайте свои зависимости и посмотрите на хороший компонент внедрения зависимостей .Длинный ответ: «Да, но не делай этого».Каркасы DI, вероятно, заставят вас передавать зависимости объектам, а не методам, и вы обнаружите, что хотите убедиться, что вы ограничиваете эти зависимости - это хорошо.

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

2 голосов
/ 08 ноября 2009

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

Используйте mock, если вы хотите изолировать класс от других. Используйте макет, поэтому держите тесты подальше от

  • файловые системы
  • базы данных
  • сети
  • объект с непредсказуемым поведением (часы, генератор случайных чисел ...)

Отдельное использование объекта от их постройки

2 голосов
/ 02 ноября 2009

[Отказ от ответственности: я работаю в Typemock]
У вас есть три варианта, два из которых требуют рефакторинга:

  1. Передавайте все аргументы - как вы уже знаете, вы можете передавать макеты / заглушки вместо "реальных" объектов.
  2. Используйте контейнер IoC - реорганизуйте свой код, чтобы использовать контейнер для извлечения объектов из вашего кода, и вы можете заменить их на mocks / stubs.
  3. Используйте Typemock Isolator (.NET), который может подделывать будущие объекты - объекты, которые создаются внутри вашего кода из тестового кода. Эта опция не требует рефакторинга, и если у вас большая база кода, она должна стоить своего кода.

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

Если вы не хотите изменять весь существующий код и / или не хотите разрабатывать свой код определенным образом, чтобы сделать его «более тестируемым» (это может привести к некоторому странному виду кода - используйте Isolator (или аналогичный инструмент если вы используете Java).

2 голосов
/ 02 ноября 2009

Просто наблюдение: вы говорите о методах, а я предпочитаю говорить о занятиях.

На этот вопрос нет общего ответа. Иногда очень важно отделить создание класса от использования.

Рассмотрим:

  • если класс использует другой, но вы хотите свободно связать их, вам следует использовать интерфейс. Теперь, если известен только интерфейс, а не конкретный тип, как первый класс должен создать экземпляр?
  • разделение классов очень важно, но не может и не должно быть сделано в каждом случае. Если сомневаешься, если должен быть отделен.
  • При использовании инъекции вам необходимо решить , кто создает и внедряет экземпляры. Скорее всего, вам пригодится среда внедрения зависимостей, такая как Spring.Net.

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

1 голос
/ 02 ноября 2009

В какой-то момент вы не собираетесь уходить от создания одного объекта от другого, но вам следует писать программное обеспечение с хорошими принципами проектирования. Например. SRP, DI и т.д ..

Если у вас много зависимостей, вы можете найти контейнер IoC, который поможет вам управлять ими всеми.

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

0 голосов
/ 03 декабря 2009

Я предпочитаю издеваться практически над всем вокруг объекта и определять поведение тестируемого объекта с точки зрения вызовов и ответов от связанных объектов.

Чтобы сделать это эффективно, требуется, чтобы ваши интерфейсы были на семантическом уровне, а не на уровне реализации в целом.

0 голосов
/ 11 ноября 2009

Я бы попытался использовать внедрение зависимостей в класс вместо того, чтобы метод класса создал объект (как рекомендует выбранный ответ). Если это не имеет смысла, подумайте о создании фабричного класса, который создает создаваемые объекты. Затем вы можете передать эту фабрику через внедрение зависимостей.

0 голосов
/ 02 ноября 2009

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

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

0 голосов
/ 02 ноября 2009

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

@user = mock_model(User)
User.stub(:create).and_return(@user)

Таким образом, теперь в ваших тестах, если вы вызываете User.create (User - это «Класс»), он всегда будет возвращать вашу предопределенную переменную @user, что позволит вам полностью контролировать, какие данные используются в ваших модульных тестах.

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

...