Должен ли я использовать тестирование "стеклянной коробки", когда это приводит к * меньшему количеству * тестов?
Например, я пишу тесты против CsvReader. Это простой класс, который перечисляет и разбивает строки текста. Единственный смысл существования - игнорировать запятые в кавычках. Это меньше, чем страница.
Проверяя класс "черным ящиком", я проверил такие вещи, как
- Что делать, если файл не существует?
- Что если у меня нет разрешения на файл?
- Что если в файле есть разрывы строк, отличные от Windows?
Но на самом деле все эти вещи являются бизнесом StreamReader. Мой класс работает, ничего не делая об этих случаях. Так что, по сути, мои тесты отлавливают ошибки, генерируемые StreamReader, и тестируют поведение, обрабатываемое платформой. Такое ощущение, что много работы даром.
Я видел связанные вопросы
- Должен ли QA тестироваться с точки зрения строго черного ящика?
- Строгость в захвате тестов для юнит-тестирования
Мой вопрос заключается в том, упускаю ли я смысл тестирования "стеклянной коробки", если я использую то, что знаю, чтобы избежать такого рода работы?
6 ответов
Это действительно зависит от интерфейса вашего CsvReader, вам нужно учитывать, что ожидает пользователь класса.
Например, если один из параметров является именем файла, а файл не существует, что должно произойти? Это не должно зависеть от того, используете ли вы потоковое считывающее устройство или нет. Модульные тесты должны проверять наблюдаемое внешнее поведение вашего класса и, в некоторых случаях, копать немного глубже и дополнительно обеспечивать охват определенных деталей реализации, например, файл закрывается после завершения чтения.
Однако вы не хотите, чтобы модульные тесты зависели от всех деталей или предполагали, что из-за деталей реализации что-то произойдет.
Все примеры, которые вы упоминаете в своем вопросе, касаются наблюдаемого поведения (в данном случае исключительных обстоятельств) вашего класса и, следовательно, должны иметь связанные с ним модульные тесты.
Я не думаю, что вы должны тратить время на тестирование вещей, которые не являются вашим кодом. Это выбор дизайна, а не выбор тестирования, будь то обработка ошибок базовой структуры или их распространение до вызывающей стороны. FWIW, я думаю, что вы правы, чтобы позволить им размножаться. Тем не менее, после того, как вы приняли решение о разработке, ваше модульное тестирование должно охватывать ваш код (и охватывать его хорошо) без тестирования базовой структуры. Использование внедрения зависимостей и фиктивного потока, вероятно, также является хорошей идеей.
[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. Из-за этого мы можем создать другой класс (возможно, фиктивный класс), который реализует IStream, но не обязательно выполняет файловый ввод / вывод. Мы можем использовать этот класс, чтобы передавать нашему читателю любые данные, которые мы хотим, без привлечения какой-либо базовой структуры. В этом случае я бы использовал MemoryStream, так как мы просто читаем из него. Однако мы хотели бы использовать фиктивный класс и предоставить ему более богатый интерфейс, который позволяет нашим тестам настраивать ответы, которые он дает. Таким образом, мы можем тестировать код, который пишем, и вообще не задействовать базовый код платформы. В качестве альтернативы мы могли бы также передать TextReader, но обычный шаблон внедрения зависимостей использует интерфейсы, и я хотел показать шаблон с интерфейсами. Возможно, лучше передать TextReader, поскольку приведенный выше код все еще зависит от реализации StreamReader.
Да, но это было бы строго для целей модульного тестирования:
Вы можете абстрагировать вашу реализацию CSV-ридера от любого конкретного StreamReader, определив интерфейс абстрактного потокового ридера и протестировав свою собственную реализацию с помощью фиктивного потокового ридера, который реализует этот интерфейс. Ваш пробный читатель, очевидно, будет невосприимчив к таким ошибкам, как несуществующие файлы, проблемы с разрешениями, различия в ОС и т. Д. Таким образом, вы будете полностью тестировать свой собственный код и сможете достичь 100% покрытия кода.
Просто к сведению, если это.NET, вы должны подумать о том, чтобы не изобретать велосипед.
Для C#
Добавьте ссылку на Microsoft.VisualBasic. Используйте фантастический класс Microsoft.VisualBasic.FileIO.TextFieldParser() для обработки ваших запросов разбора CSV.
Microsoft уже проверила это, так что вам не придется.
Наслаждаться.
Я склонен согласиться с tvanfosson: если вы наследуете от StreamReader и каким-то образом расширяете его, ваши модульные тесты должны использовать только ту функциональность, которую вы добавили или изменили. В противном случае вы будете тратить много времени и когнитивной энергии на написание, чтение и ведение тестов, которые не приносят никакой пользы.
Хотя markj прав, что тесты должны охватывать "наблюдаемое внешнее поведение" класса, я думаю, что уместно рассмотреть, откуда происходит это поведение. Если это поведение через наследование от другого (предположительно, модульно протестированного) класса, то я не вижу никакой выгоды в добавлении собственных модульных тестов. OTOH, если это поведение с помощью композиции, то это может оправдать некоторые тесты, чтобы убедиться, что проходы работают правильно.
Я предпочел бы провести модульное тестирование определенной функциональности, которую вы изменяете, а затем написать интеграционные тесты, которые проверяют наличие ошибок, но в контексте потребностей бизнеса, которые вы в конечном итоге поддерживаете.
Вы всегда должны управлять ошибками, которые выдает ваша структура; Таким образом, ваше приложение будет устойчивым и не будет зависать от катастрофических ошибок...