2008-10-14 2 views
3

Например, я пишу тесты против CsvReader. Это простой класс, который перечисляет и разбивает строки текста. Его единственный raison d'être игнорирует запятые внутри кавычек. Это меньше, чем страница.Должен ли я использовать тестирование «стеклянного ящика», когда он приводит к * меньшему количеству * тестов?

Под «черный ящик» тестирования класс, я проверил вещи, как

  • Что делать, если файл не существует?
  • Что делать, если у меня нет разрешения на файл?
  • Что делать, если файл имеет не-Windows-перерывы?

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

Я видел соответствующие вопросы

Мой вопрос, я пропускаю пункт тестирования «стеклянный ящик», если я использую то, что я знаю избегать этот вид работы?

ответ

3

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

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

Однако вы не хотите, чтобы Unit Tests зависели от всех деталей или предполагали, что из-за детали реализации что-то произойдет.

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

+0

Я согласен с tvan, что необходимо нарисовать линию, но в конечном счете, вы правы, линия должна быть нарисована с точки зрения клиента. Например, я не перечислил тест «закрыть после использования», потому что не было никакого вопроса, который можно было бы ожидать. – harpo 2008-10-14 23:23:47

0

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

+0

Но, чтобы быть ясным, мой класс не улавливает ошибок, сбрасываемых каркасом. Я считаю это обязанностью вызывающего, чтобы сделать это, поскольку он, скорее всего, знает, что можно сделать по этому поводу. Таким образом, все мои тесты показывают, что ошибки выбрасываются. – harpo 2008-10-14 22:25:27

2

Да, но это было бы строго быть для целей модульного тестирования:

Вы могли бы абстрактные реализации читатель CSV из какой-либо конкретной StreamReader путем определения интерфейса чтения абстрактного потока и тестирования собственной реализации с помощью считывателя mock stream, который реализует этот интерфейс. Ваш макет читателя, очевидно, будет невосприимчив к ошибкам, таким как несуществующие файлы, проблемы с разрешениями, различия ОС и т. Д. Таким образом, вы должны полностью тестировать свой собственный код и получать 100% -ный охват кода.

+0

Хммм ... это звучит сложно. Как бы то ни было, моя проблема слишком большая. Если я правильно вас понимаю, я мог бы добиться того же самого эффекта, ограничивая данные теста только теми случаями, которые, как я знаю, покрывают мой фактический код. – harpo 2008-10-14 23:16:45

3

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

[EDIT] Пример внедрения зависимостей (см ссылку выше для получения дополнительной информации)

Не с помощью инъекции зависимостей мы имеем:

public class CvsReader { 
    private string filename; 
    public CvsReader(string filename) 
    { 
     this.filename = filename; 
    } 

    public string Read() 
    { 
     StreamReader reader = new StreamReader(this.filename); 
     string contents = reader.ReadToEnd(); 
     .... do some stuff with contents... 
     return contents; 
    } 
} 

С инъекции зависимостей (инъекции конструктора) мы делаем:

public class CvsReader { 
    private IStream stream; 
    public CvsReader(IStream stream) 
    { 
     this.stream = stream; 
    } 

    public string Read() 
    { 
     StreamReader reader = new StreamReader(this.stream); 
     string contents = reader.ReadToEnd(); 
     ... do some stuff with contents ... 
     return contents; 
    } 
} 

Это позволяет CvsReader быть более легко проверяемым. Мы передаем экземпляр, реализующий интерфейс, от которого мы зависим в конструкторе, в этом случае IStream.Из-за этого мы можем создать еще один класс (возможно, класс mock), который реализует IStream, но не обязательно выполняет ввод/вывод файлов. Мы можем использовать этот класс для подачи нашему читателю любых данных, которые мы хотим, без участия какой-либо базовой структуры. В этом случае я бы использовал MemoryStream, так как мы просто читаем его. Тем не менее, мы хотели использовать класс mock и дать ему более богатый интерфейс, который позволяет нашим тестовым настройкам настраивать ответы, которые он дает. Таким образом, мы можем протестировать код, который мы пишем, и вообще не включать базовый код. В качестве альтернативы мы могли бы также передать TextReader, но обычный шаблон инъекции зависимостей использует интерфейсы, и я хотел показать шаблон с интерфейсами. Возможно, передача в TextReader будет лучше, поскольку вышеприведенный код все еще зависит от реализации StreamReader.

+0

Да, если я знаю, что некоторые случаи гораздо более маловероятны, чем другие, время может быть лучше потрачено в другом месте. Что касается «инъекции зависимостей», я все еще новичок в этой концепции. – harpo 2008-10-14 23:21:19

1

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

Хотя markj является правильным, что тесты должны охватывать «наблюдаемое внешнее поведение» класса, я думаю, что уместно рассмотреть , где такое поведение. Если это поведение через наследование от другого (предположительно, проверенного модулем) класса, то я не вижу никакой пользы при добавлении собственных тестов . OTOH, если это поведение с помощью композиции, то может оправдывать некоторые тесты, чтобы обеспечить прохождение работы.

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

1

Просто FYI, если это .NET, вы должны подумать о том, чтобы не изобретать колесо.

Для C#

Добавить ссылку на Microsoft.VisualBasic Использование фантастического класса Microsoft.VisualBasic.FileIO.TextFieldParser() для обработки ваших потребностей синтаксического анализа CSV.

Microsoft уже проверила его, так что вам не придется.

Наслаждайтесь.