TDD: Можно ли проводить интеграционные тесты, но нет юнит-тестов? - PullRequest
5 голосов
/ 15 февраля 2012

Технологический стек: .NET 4, C #, NUnit

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

public class BaseFile
{
    public String Path { get; set; }

    public BaseFile()
    {
        Path = String.Empty;
    }

    public BaseFile(String path)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("File not found.", path);
        }

        Path = path;
    }
}

Есть ли ценность в тестировании этих методов? Если да, то как я могу абстрагироваться от обращений к файловой системе?

Мой другой вопрос - как протестировать подкласс, специфичный для типа файла изображения (~ 200 МБ). Я искал сайт и нашел похожих вопросов , но ни один из них не касается размеров файлов, с которыми я работаю в этом проекте. Возможно ли, чтобы у класса были интеграционные тесты (с использованием «золотого файла»), но без юнит-тестов? Как я мог строго следовать методам TDD и сначала написать неудачный тест в этом случае?

Ответы [ 5 ]

4 голосов
/ 15 февраля 2012

В ответ на ваш первый вопрос, да, есть смысл в тестировании этих методов. Я опубликовал библиотеку, которая позволяет делать это точно, не затрагивая файловую систему: https://bitbucket.org/mylesmcdonnell/mpm.io/wiki/Home

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

3 голосов
/ 15 февраля 2012

Как я мог строго следовать методам TDD и сначала написать провальный тест в этом случае?

Легко! Вы издеваетесь над файловой системой:)

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

public interface IFileStore
{
    Boolean FileExists(String path);
}

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

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

public class BaseFile
{
    private IFileStore _fileStore
    public IFileStore FileStore
    {
        get
        {
            return _fileStore ?? (_fileStore = new ConcreteFileStore());
        }
        set
        {
            _fileStore = value;
        }
    }

    //SNIP...
}

Теперь у вас есть тестируемая реализация, и вам не придется полагаться на какие-либо «золотые» файлы.

1 голос
/ 15 февраля 2012

Насмешка над простыми вызовами файловой системы с использованием интерфейсов кажется излишним.То же самое касается насмешек над текущим временем с помощью ITimeService.Я склонен использовать Func или Action, потому что это намного проще:

public static Func<string, bool> FileExists = System.IO.File.Exists;
public static Func<DateTime> GetCurrentTime = () => DateTime.Now;

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

1 голос
/ 15 февраля 2012

Есть смысл в тестировании этих методов

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

Определить список ожидаемого поведения

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

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

Расширение набора автоматических регрессионных тестов

Учтите, что кто-то видит, как какой-то код вызывает исключение в определенном состоянии, но он думает, что разумнее сделать что-то другое (Исправьте ошибку, но добавьте новое свойство IsValid, чтобы потребители могли знать, была ли успешной конструкция / инициализация).Если они сделают такое изменение, тест очень быстро привлечет внимание к изменению.За тем, как все было, было сознательное и преднамеренное решение, и люди, возможно, стали полагаться на существующее поведение - это изменение требует дальнейшего обсуждения, прежде чем его можно будет принять.

Что касается второй части вашеговопрос, я думаю, что Джош и Майлз уже дали здравый совет.

0 голосов
/ 15 февраля 2012

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

Только интеграционный тест может точно сказать определенные вещи.(Интеграционные тесты также имеют свои недостатки!).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...