C# заглушки. Интерфейс для каждого тестируемого объекта?

Я видел несколько ответов о том, "как заглушить ваши классы, чтобы вы могли контролировать, что происходит в SUT".

Они говорят одно:

Создайте интерфейс и внедрите этот интерфейс, используя внедрение зависимостей, и создайте заглушку, используя тот же интерфейс, который вы затем вводите в SUT.

Однако то, что я узнал на своих предыдущих рабочих местах:

Если вы тестируете модуль, вы тестируете все классы / функциональность.

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

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

Как видно из приведенного ниже примера, это "путь" или я что-то упустил в процессе модульного тестирования?

Как примечание: я использую VS2012 Express. Это означает отсутствие фейкерской структуры. Я использую "стандартную" систему модульного тестирования VS2012.

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

IFoo.cs

public interface IFoo
{
    string GetName();
}

Foo.cs

public class Foo : IFoo
{
    public string GetName()
    {
        return "logic goes here";
    }
}

IBar.cs:

public interface IBar : IFoo
{
    IFoo GetFoo();
}

Bar.cs:

public class Bar : IBar
{
    public string GetName()
    {
        return "logic goes here";
    }

    public IFoo GetFoo()
    {
        return null; // some instance of IFoo
    }
}

IBaz.cs:

public interface IBaz
{
    IBar GetBar();
}

Baz.cs:

public class Baz
{
    public IBar GetBar()
    {
        return null; // some instance of IBar
    }
}

5 ответов

Решение

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

Рассмотрим простой пример. Ты тестируешь класс A который принимает зависимости от классов B а также C, Для юнит-тестов A чтобы работать, надо издеваться B а также C - тебе понадобиться IB а также IC (или базовые классы / ш виртуальные члены). Тебе нужно IA? Нет, по крайней мере, не для этого теста. И если A становится зависимостью от некоторого другого класса, абстрагирование его за интерфейс /базовый класс не требуется.

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

На мой взгляд, вы не должны создавать интерфейсы только для целей модульного тестирования. Если вы начнете добавлять абстракции кода, чтобы понравиться инструментам, то они не помогут вам быть более продуктивным. Код, который вы пишете, в идеале должен удовлетворять конкретным бизнес-целям / потребностям - либо напрямую, либо косвенно, облегчая поддержку или развитие базы кода.

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

Решение (в качестве подсказки Роберту за то, что он был первым в комментариях) состоит в том, чтобы использовать фреймворк для генерации совместимого типа замещения во время выполнения. Затем фиктивные рамки позволяют вам убедиться, что тестируемый класс правильно взаимодействовал с замещенным фиктивным элементом. Мок, как уже упоминалось, шикарный выбор. Rhino.Mocks и NMock - две другие популярные платформы. Typemock Isolator подключается к профилировщику и входит в число наиболее мощных опций (позволяет заменять даже не виртуальные частные члены), но является коммерческим инструментом.

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

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

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

Я бы пошел по пути виртуального метода. Создание интерфейсов для каждого класса, который нужно протестировать, становится действительно обременительным, особенно когда вам нужны такие инструменты, как Resharper, для "перехода к реализации" каждый раз, когда вы хотите увидеть определение метода. Кроме того, каждый раз, когда изменяется сигнатура метода или добавляется новое свойство или метод, возникает необходимость в управлении и изменении обоих файлов.

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