Должен ли я использовать тестирование “стеклянного ящика”, когда это приводит к * меньшему* количеству * тестов?
-
03-07-2019 - |
Вопрос
Например, я пишу тесты для CSVReader.Это простой класс, который перечисляет и разбивает строки текста.Его единственный raison d'être игнорирует запятые в кавычках.Это меньше страницы.
Тестируя класс "черным ящиком", я проверил такие вещи, как
- Что делать, если файл не существует?
- Что, если у меня нет прав доступа к файлу?
- Что делать, если файл содержит разрывы строк, отличные от Windows?
Но на самом деле, все эти вещи - дело программы StreamReader.Мой класс работает, ничего не делая с этими случаями.Итак, по сути, мои тесты отлавливают ошибки, выдаваемые StreamReader, и тестируют поведение, обрабатываемое фреймворком.Такое ощущение, что столько работы впустую.
Я видел связанные с этим вопросы
- Должен ли QA тестироваться строго с точки зрения черного ящика?
- Строгость при сборе тестовых примеров для модульного тестирования
Мой вопрос в том, упускаю ли я смысл тестирования "стеклянной коробки", если использую то, что я знаю, для избегать такого рода работа?
Решение
Это действительно зависит от интерфейса вашего CSVReader, вам нужно учитывать, чего ожидает пользователь класса.
Например, если одним из параметров является имя файла, а файл не существует, что должно произойти?Это не должно зависеть от того, используете ли вы программу чтения потоков или нет.Модульные тесты должны проверять наблюдаемое внешнее поведение вашего класса, а в некоторых случаях копать немного глубже и дополнительно обеспечивать освещение определенных деталей реализации, напримерфайл закрывается, когда программа чтения завершает работу.
Однако вы не хотите, чтобы модульные тесты зависели от всех деталей или предполагали, что из-за деталей реализации что-то произойдет.
Все примеры, которые вы упоминаете в своем вопросе, связаны с наблюдаемым поведением (в данном случае исключительными обстоятельствами) вашего класса и, следовательно, должны иметь связанные с ними модульные тесты.
Другие советы
Я не думаю, что вам следует тратить время на тестирование того, что не является вашим кодом.Это выбор дизайна, а не тестирования, обрабатывать ли ошибки базового фреймворка или позволить им распространяться до вызывающей стороны.ФУ-у-у, я думаю, ты прав, позволяя им размножаться.Однако после того, как вы приняли проектное решение, модульное тестирование должно охватывать ваш код (и охватывать его хорошо) без тестирования базового фреймворка.Используя внедрение зависимостей и пробный поток, вероятно, тоже хорошая идея.
[РЕДАКТИРОВАТЬ] Пример внедрения зависимости (смотрите ссылку выше для получения дополнительной информации)
Не используя внедрение зависимостей, мы имеем:
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;
}
}
С помощью внедрения зависимостей (constructor injection) мы делаем:
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 Reader от любого конкретного StreamReader, определив абстрактный интерфейс stream Reader и протестировав свою собственную реализацию с помощью mock stream reader, который реализует этот интерфейс.Очевидно, что ваш макет Reader будет невосприимчив к ошибкам, таким как несуществующие файлы, проблемы с разрешениями, различия в операционной системе и т.д.Таким образом, вы будете полностью тестировать свой собственный код и сможете достичь 100% покрытия кода.
Я склонен согласиться с тванфоссоном:Если вы наследуете от StreamReader и каким-то образом расширяете его, ваши модульные тесты должны использовать только ту функциональность, которую вы добавили или изменили.В противном случае вы потратите уйму времени и когнитивной энергии на написание, чтение и поддержку тестов, которые не приносят никакой пользы.
Хотя markj прав в том, что тесты должны охватывать "наблюдаемое внешнее поведение" класса, я думаю, что уместно рассмотреть где такое поведение исходит от.Если это поведение через наследование от другого (предположительно модульно протестированного) класса, то я не вижу никакой пользы в добавлении вашего собственного единица измерения тесты.OTOH, если это поведение через композицию, то это мог бы обоснуйте некоторые тесты, чтобы убедиться, что проходы работают должным образом.
Я бы предпочел модульное тестирование конкретной функциональности, которую вы изменяете, а затем написать интеграция тесты, которые проверяют наличие ошибок, но в контексте бизнес-потребностей, которые вы в конечном счете поддерживаете.
Просто К вашему сведению, если это .NET, вам следует подумать о том, чтобы не изобретать велосипед заново.
Для C#
Добавьте ссылку на Microsoft.VisualBasic Используйте фантастический класс Microsoft.VisualBasic.FileIO.TextFieldParser() для обработки ваших потребностей в синтаксическом анализе CSV.
Microsoft уже протестировала это, так что вам не придется этого делать.
Наслаждайся.
Вы всегда должны управлять ошибками, которые выдает ваш фреймворк;таким образом, ваше приложение является надежным и не вылетает из-за катастрофических ошибок...