Фабричные методы модульного тестирования, которые имеют конкретный класс в качестве возвращаемого типа - PullRequest
33 голосов
/ 30 июня 2009

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

Что я должен проверить, если фабрика возвращает конкретные типы (потому что в данный момент нет необходимости в использовании интерфейсов)? В настоящее время я делаю что-то вроде следующего:

[Test]
public void CreateSomeClassWithDependencies()
{
    // m_factory is instantiated in the SetUp method
    var someClass = m_factory.CreateSomeClassWithDependencies();

    Assert.IsNotNull(someClass);
}

Проблема в том, что Assert.IsNotNull кажется несколько избыточным.

Кроме того, мой метод фабрики может устанавливать зависимости этого конкретного класса следующим образом:

public SomeClass CreateSomeClassWithDependencies()
{
    return new SomeClass(CreateADependency(), CreateAnotherDependency(),
                         CreateAThirdDependency());
}

И я хочу убедиться, что мой метод фабрики правильно устанавливает все эти зависимости. Разве нет другого способа сделать это, чтобы сделать свойства зависимостей public/internal, которые я затем проверю в модульном тесте? (Я не большой поклонник модификации испытуемых в соответствии с тестированием)

Редактировать: В ответ на вопрос Роберта Харви я использую NUnit в качестве основы для модульного тестирования (но я бы не подумал, что это будет иметь большое значение)

Ответы [ 5 ]

34 голосов
/ 30 июня 2009

Часто нет ничего плохого в создании открытых свойств, которые можно использовать для тестирования на основе состояния. Да: это код, который вы создали для включения тестового сценария, но вредит ли это вашему API? Возможно ли, что другие клиенты сочтут это свойство полезным позже?

Существует тонкая грань между тестовым кодом и Test-Driven Design. Мы не должны вводить код, который не имеет другого потенциала, кроме как удовлетворить требования тестирования, но вполне нормально вводить новый код, который следует общепринятым принципам проектирования. Мы разрешаем тестировать диск наш дизайн - вот почему мы называем его TDD:)

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

Кроме того, я второй ответ Надера:)

24 голосов
/ 30 июня 2009

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

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

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

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

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

Однако, используя приведенный вами пример, вы можете получить класс из фабрики. Затем переопределите / mock CreateADependency(), CreateAnotherDependency() и CreateAThirdDependency(). Теперь, когда вы вызываете CreateSomeClassWithDependencies(), вы можете определить , были ли созданы правильные зависимости.

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

3 голосов
/ 30 июня 2009

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

Глядя на ваш пример кода, да, Assert not null кажется избыточным, в зависимости от того, как вы спроектировали фабрику, некоторые будут возвращать нулевые объекты с фабрики, а не исключать.

3 голосов
/ 30 июня 2009

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

0 голосов
/ 24 октября 2011

Насколько я понимаю, вы хотите проверить, правильно ли построены зависимости и переданы новому экземпляру?

Если бы я не смог использовать фреймворк, такой как Google Guice, я бы, вероятно, сделал это примерно так (здесь, используя JMock и Hamcrest):

@Test
public void CreateSomeClassWithDependencies()
{
    dependencyFactory = context.mock(DependencyFactory.class);
    classAFactory = context.mock(ClassAFactory.class);

    myDependency0 = context.mock(MyDependency0.class);
    myDependency1 = context.mock(MyDependency1.class);
    myDependency2 = context.mock(MyDependency2.class);
    myClassA = context.mock(ClassA.class);

    context.checking(new Expectations(){{
       oneOf(dependencyFactory).createDependency0(); will(returnValue(myDependency0));
       oneOf(dependencyFactory).createDependency1(); will(returnValue(myDependency1));
       oneOf(dependencyFactory).createDependency2(); will(returnValue(myDependency2));

       oneOf(classAFactory).createClassA(myDependency0, myDependency1, myDependency2);
       will(returnValue(myClassA));
    }});

    builder = new ClassABuilder(dependencyFactory, classAFactory);

    assertThat(builder.make(), equalTo(myClassA));
}

(если вы не можете смоделировать ClassA, вы можете назначить немодальную версию myClassA, используя new)

...