Должен ли я использовать контейнер инъекции зависимостей в модульных тестах? [Дубликат]

Когда вы объявляете ссылочную переменную (т. е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int:

int x;
x = 10;

В этом примере переменная x является int, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.

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

Integer num;
num = new Integer(10);

Первая строка объявляет переменную с именем num, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».

Во второй строке ключевое слово new используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования . (точка).

Exception, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num. Перед созданием объекта вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.

Например, вы можете имеют следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

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

doSomething(null);

В этом случае obj имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

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

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Наконец, Как определить исключение & amp; причина использования Трассировки стека

8
задан Steven 15 September 2015 в 20:42
поделиться

2 ответа

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

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

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

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

private ClassUnderTest CreateClassUnderTest(
    ILogger logger = null, IMailSender mailSender = null, IEventPublisher publisher = null)
{
    return new ClassUnderTest(
        logger ?? new FakeLogger(),
        mailSender ?? new FakeMailer(),
        publisher ?? new FakePublisher());
}

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

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

public void Test() {
    // Arrange
    var logger = new ListLogger();

    ClassUnderTest sut = CreateClassUnderTest(logger: logger);

    // Act
    sut.DoSomething();

    // Arrange
    Assert.IsTrue(logger.Count > 0);    
}

Если вы заинтересованы в том, чтобы узнать, как писать тесты, читаемые, заслуживающие доверия и поддерживаемые ( RTM ), я советую вы должны прочитать книгу Роя Ошерове «Искусство модульного тестирования» (второе издание). Это очень помогло мне.

9
ответ дан Steven 18 August 2018 в 00:26
поделиться

Для истинных модульных тестов (т. е. те, которые проверяют только один класс и издеваются над всеми его зависимостями), нет смысла использовать структуру DI. В этих тестах:

  • , если вы обнаружите, что у вас есть много повторяющихся кодов для new, используя экземпляр вашего класса со всеми созданными вами mocks, одна полезная стратегия - создайте все свои mocks и создайте экземпляр для субъекта-теста в вашем методе установки (все это могут быть частные поля экземпляра), а затем область «организовать» каждого отдельного теста просто должна вызвать соответствующий код Setup() на методы, которые он должен издеваться. Таким образом, вы получаете только один оператор new PersonController(...) для каждого тестового класса.
  • , если вам нужно создать много объектов домена / данных, полезно создавать объекты Builder, которые начинаются со здравых значений для тестирования. Поэтому вместо того, чтобы ссылаться на огромный конструктор по всему вашему коду с кучей поддельных значений, вы в основном просто вызываете, например, var person = new PersonBuilder().Build(), возможно, с помощью нескольких цепочек вызовов для фрагментов данных, которые вам особенно интересны в этот тест. Вы также можете быть заинтересованы в AutoFixture , но я никогда не использовал его, поэтому я не могу ручаться за него.

Если вы пишете Integration , где вам нужно протестировать взаимодействие между несколькими частями системы, но вы все равно должны иметь возможность издеваться над определенными частями, подумайте о создании классов Builder для своих служб, чтобы вы могли сказать, например var personController = new PersonControllerBuilder.WithRealDatabase(connection).WithAuthorization(new AllowAllAuthorizationService()).Build().

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

var user = new PersonBuilder().Build();
using(Login.As(user))
{
     var controller = Container.Get<PersonController>();
     var result = controller.GetCurrentUser();
     Assert.AreEqual(result.Username, user.Username)
}
14
ответ дан StriplingWarrior 18 August 2018 в 00:26
поделиться
  • 1
    Я считаю, что Object Mother - это имя шаблона построителя в случае модульных тестов. Хороший ответ кстати. +1 – Steven 15 September 2015 в 20:41
  • 2
  • 3
    @sotn: Это не похоже на тот же вопрос, и я не вижу там никаких ответов, которые, похоже, противоречат этому. Можете ли вы напрямую ссылаться на ответ, о котором говорите, и объяснять, как это «противоположно»? к этому? – StriplingWarrior 27 November 2017 в 17:42
  • 4
    Кстати, AutoFixture и его расширения великолепны. Я очень рекомендую. Я использую его вместе с moq, он позволяет вам возвращать макетные объекты со всеми их зависимостями, издевающимися над ними. – Yamaç Kurtuluş 10 June 2018 в 23:49
  • 5
    @ YamaçKurtuluş: Спасибо, что поделились. За эти годы, когда я опубликовал этот ответ, у меня была возможность использовать AutoFixture, и я согласен. Я связываю его с расширением Automoq и замораживаю макет объектов в моем методе настройки. И добавив некоторые настройки, я избегаю необходимости для объектов Builder / Factory для моих моделей. Мне не нужно загромождать свой код бессмысленными строками и числами. Здорово. – StriplingWarrior 11 June 2018 в 00:33
Другие вопросы по тегам:

Похожие вопросы: