2012-02-15 5 views
5

Технология Стек: .NET 4, C#, NUnitTDD: Возможно ли иметь интеграционные тесты, но нет модульных тестов?

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

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

+4

Если TDD трудно применять или неадекватно, не применяйте его. Это не серебряная пуля. – CharlesB

+1

@CharlesB, я согласен. К сожалению, это чувство часто используется как оправдание для того, чтобы не использовать TDD, когда это действительно так и выгодно. Иногда есть большая кривая или много работы, но это обычно окупается. –

+1

@CharlesB Не уверен, что я согласен с этим. Проблема в том, что тогда этот класс, который выполняет некоторую работу, не проверяется, и поэтому ваша уверенность в его изменении уменьшается. Проблема в том, что его непросто издеваться над статическими методами в System.IO, но это не значит отказаться от тестирования, это просто означает, что вам нужно сделать немного больше работы, чтобы сделать их проверяемыми. Вот почему MS представляет System.Web.HttpContextBase, чтобы решить проблемы, не имея возможности высмеять HttpContext. Нам не нужно тестировать httpContext, только что наш код правильно взаимодействует с ним. – Andy

ответ

4

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

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

+0

Благодарим вас за отправку этого исходного кода. Я отмечаю ваш пост как ответ, потому что вы предоставили лучшее общее решение. – Noren

3

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

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

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

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

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

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

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

    //SNIP... 
} 

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

+0

Благодарим вас за сообщение. Пока я собираюсь реализовать ваше решение, пока мне не понадобится больше вызовов System.IO, после чего я буду использовать код Myles. – Noren

0

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

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

+0

Правильно. Я не подразумеваю, что вы не должны делать интеграционный тест. Скорее, это TDD все еще можно сделать даже на тех вещах, которые мы обычно не рассматриваем как файловые системы. – Josh

+0

Я не имел в виду девальвировать ваше объяснение. Я ссылался на него, потому что это было хорошо. – usr

1

Существует значение в тестировании этих методов

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

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

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

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

Grow набор автоматизированных тестов регрессии

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

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

+0

Вы сделали хорошие баллы. Я считаю, что оставлю в тестах эти простые методы. – Noren

1

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

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

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

Смежные вопросы