Какие объекты имитировать при выполнении TDD

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

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

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

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


person Jamie Dixon    schedule 02.11.2009    source источник
comment
каждый объект, созданный внутри этого метода, будет передан. Возможно, вы захотите перефразировать это. Это сбивает с толку. Я думаю, вы спрашиваете, должны ли они быть переданы вместо создания экземпляра внутри метода. Будет немного больше смысла, если это будет считаться альтернативой.   -  person S.Lott    schedule 02.11.2009


Ответы (10)


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

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

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

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.

person Mark Seemann    schedule 02.11.2009

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

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

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

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

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

person Philip Rieck    schedule 02.11.2009

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

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

Рассмотреть возможность:

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

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

person Stefan Steinegger    schedule 02.11.2009

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

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

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

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

person Dror Helper    schedule 02.11.2009

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

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

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

Отделить использование объекта от его конструкции

person philant    schedule 08.11.2009

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

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

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

person Johnno Nolan    schedule 02.11.2009

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

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

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

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

person Mike Trpcic    schedule 02.11.2009

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

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

person FinnNk    schedule 02.11.2009

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

person Frank Schwieterman    schedule 11.11.2009

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

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

person kyoryu    schedule 03.12.2009