Модульное тестирование абстрактной фабрики, которая принимает параметры

Учитывая абстрактную фабричную реализацию:

public class FooFactory : IFooFactory {
   public IFoo Create(object param1, object param2) {
      return new Foo(param1, param2);
   }
}

Какие модульные тесты будут написаны для этого класса? Как я могу проверить, что param1 и param2 были перенаправлены на создание Foo? Должен ли я сделать эти публичные свойства Foo? Разве это не нарушает инкапсуляцию? Или я должен оставить это для интеграционного тестирования?

5 ответов

Решение

Вот как я бы написал один из нескольких модульных тестов для такой фабрики (с xUnit.net):

[Fact]
public void CreateReturnsInstanceWithCorrectParam1()
{
    var sut = new FooFactory();
    var expected = new object();
    var actual = sut.Create(expected, new object());
    var concrete = Assert.IsAssignableFrom<Foo>(actual);
    Assert.Equal(expected, concrete.Object1);
}

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

Давайте предположим, что Foo предоставляет этот публичный API:

public class Foo : IFoo
{
    public Foo(object param1, object param2);

    public void MethodDefinedByInterface();

    public object Object1 { get; }
}

В то время как Object1 Свойство слегка нарушает закон Деметры, оно не портит инварианты класса, потому что оно доступно только для чтения.

Кроме того, Object1 свойство является частью конкретного класса Foo, а не интерфейса IFoo:

public interface IFoo
{
    void MethodDefinedByInterface();
}

Как только вы поймете, что в слабосвязанном API конкретные элементы являются деталями реализации, такие свойства только для чтения только для чтения оказывают очень низкое влияние на инкапсуляцию. Подумайте об этом таким образом:

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

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

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

  1. Из тестов конкретного класса Foo мы знаем, что он правильно использует / взаимодействует со своими параметрами конструктора.
  2. Из этих тестов мы также знаем, что параметр конструктора доступен только для чтения.
  3. Из теста FooFactory мы знаем, что он возвращает экземпляр конкретного класса Foo.
  4. Кроме того, мы знаем из тестов метода Create, что его параметры правильно передаются в конструктор Foo.
  5. QED

Ну, предположительно эти параметры делают возвращаемое IFoo есть что-то правдивое в этом. Проверьте, что это правда о возвращенном экземпляре.

Вы могли бы сделать FooFactory универсальным классом, который ожидает Foo в качестве параметра, и указать, что это макет. Вызов factory.Create (...) создает экземпляр макета, и вы можете подтвердить, что макет получил ожидаемые вами аргументы.

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

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

Это зависит от того, что Foo делает с двумя входными объектами. Проверьте что-то, что Foo делает с ними, к чему можно легко обратиться. Например, если к объектам вызываются методы (включая методы получения или установки), предоставьте имитации или заглушки, которые вы можете проверить, вызвав ожидаемые вещи. Если на IFoo есть что-то, с чем можно протестировать, вы можете передать известные значения через фиктивные объекты для проверки после создания. Сами объекты не должны быть общедоступными, чтобы убедиться, что вы их пропустили.

Я также мог бы проверить, что ожидаемый тип (Foo) возвращается, в зависимости от того, зависят ли другие протестированные поведения от различий между реализациями IFoo.

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

Другие вопросы по тегам