Должен ли я использовать тестирование “стеклянного ящика”, когда это приводит к * меньшему* количеству * тестов?

StackOverflow https://stackoverflow.com/questions/203075

Вопрос

Например, я пишу тесты для CSVReader.Это простой класс, который перечисляет и разбивает строки текста.Его единственный raison d'être игнорирует запятые в кавычках.Это меньше страницы.

Тестируя класс "черным ящиком", я проверил такие вещи, как

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

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

Я видел связанные с этим вопросы

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

Это было полезно?

Решение

Это действительно зависит от интерфейса вашего 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 уже протестировала это, так что вам не придется этого делать.

Наслаждайся.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top