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

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

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

[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 в качестве своей среды модульного тестирования (но я бы не подумал, что это будет иметь слишком большое значение)


person jpoh    schedule 30.06.2009    source источник
comment
Какую тестовую среду вы используете?   -  person Robert Harvey    schedule 30.06.2009
comment
Некоторые среды тестирования требуют, чтобы ваши классы были виртуальными, чтобы среда тестирования могла их наследовать. Некоторые нет. Огромная разница.   -  person Robert Harvey    schedule 30.06.2009


Ответы (5)


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

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

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

Кроме того, я ответил второму надеру :)

person Mark Seemann    schedule 30.06.2009
comment
+1 за тестирование нашего дизайна. Использование свойств для предоставления зависимостей помогает настроить зависимости и сделать их очевидными. Оба хороши. - person Nader Shirazie; 30.06.2009
comment
@nader: Хорошо сказано - это то, что я пытался сказать :) - person Mark Seemann; 30.06.2009
comment
+1 Мне так часто нужно сделать что-то общедоступным, чтобы я мог проводить модульное тестирование только для того, чтобы обнаружить, что мне это нужно позже в коде, и другим людям это также нужно. - person uriDium; 09.02.2010
comment
У +1 был тот же вопрос о введении свойств в API для включения UT. - person Tomasz Jaskuλa; 25.02.2010
comment
Иногда НЕ нормально открывать внутренности класса только для тестирования. Например, вы не хотите случайно утечь внутренний изменяемый объект, от которого зависит корректность класса. Несколько причин, по которым это может быть плохо: (а) нарушение принципа инкапсуляции и (б) безопасность класса (другой, даже вредоносный, код может изменить класс непреднамеренным и нежелательным образом). Как вы тестируете фабрики, если не хотите утечки абстракции? - person Jim Hurne; 28.01.2011
comment
@ Джим Херн: я согласен, и я никогда не говорил ничего другого. Существует большая разница между предоставлением доступного только для чтения свойства, состояние которого можно проверить, и затем предоставлением изменяемой ссылки. Мы должны делать последнее только в том случае, если оно имеет смысл в целом (что часто не имеет смысла). - person Mark Seemann; 28.01.2011
comment
В зависимости от того, насколько широко используется ваш API, я думаю, что спекулятивное добавление этих методов, потому что клиент может найти их полезными, в долгосрочной перспективе приведет вас к проблемам. Каждый раз, когда вы это делаете, вы должны поддерживать и тестировать этот метод или проходить через процесс устаревания — на случай, если клиент где-то использует этот метод. Это похоже на большие затраты при очень небольшой пользе; особенно если вы делаете это постоянно или часто в своем API. Мой совет будет заключаться в том, чтобы ваш API был как можно меньше и теснее - либо он нужен клиенту, и ваш тест, либо нет. - person lexicalscope; 24.10.2011
comment
Это типичное оправдание Microsoft для того, чтобы сделать все internal sealed. Хотя я признаю, что это необходимо учитывать, если вы отправляете продукт тысячам или миллионам потребителей, я утверждаю, что этот аргумент по-прежнему неприменим к большинству производимого кода. Большая часть кода, производимого во всем мире, имеет очень ограниченный или хорошо известный набор потребителей, поэтому я не согласен с тем, что вы можете применять аргумент Microsoft к большинству кодовых баз. - person Mark Seemann; 24.10.2011

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

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

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

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

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

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

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

person Nader Shirazie    schedule 30.06.2009
comment
Мне нравится ответ, и эта книга — подарок бога, если вам приходится иметь дело с устаревшим или близким к устаревшему (.net 1.1) кодом без тестов. Также хорошо, если вы работаете в команде, где модульное тестирование ранее не проводилось, и у вас есть много неиспользуемого кода, который вы хотите протестировать, эта книга очень полезна. - person ElvisLives; 19.01.2011

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

person Robert    schedule 30.06.2009
comment
Это здорово, но что, если конкретные типы, созданные фабрикой, сами имеют частные свойства, которые нужно проверять? - person Neil; 25.01.2019

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

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

person Sam Saffron    schedule 30.06.2009

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

Если бы я не мог использовать такую ​​​​инфраструктуру, как 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)

person lexicalscope    schedule 24.10.2011