Следует ли тестировать внутреннюю реализацию или только тестировать публичное поведение? - PullRequest
43 голосов
/ 13 мая 2009

Дано программное обеспечение, где ...

  • Система состоит из нескольких подсистем
  • Каждая подсистема состоит из нескольких компонентов
  • Каждый компонент реализован с использованием множества классов

... Мне нравится писать автоматические тесты для каждой подсистемы или компонента.

Я не пишу тест для каждого внутреннего класса компонента (за исключением того, что каждый класс вносит вклад в открытую функциональность компонента и поэтому тестируется / тестируется извне через открытый API компонента).

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

Я думаю, что эта политика отличается от документа, подобного Рефакторинг кода теста , в котором говорится что-то вроде ...

  • "... модульное тестирование ..."
  • "... тестовый класс для каждого класса в системе ..."
  • "... соотношение тестового кода / производственного кода ... в идеале считается равным 1: 1 ..."

... со всем, что, я полагаю, я не согласен (или, по крайней мере, не практикую).

Мой вопрос: если вы не согласны с моей политикой, объясните почему? В каких случаях этого уровня тестирования недостаточно?

В итоге:

  • Публичные интерфейсы тестируются (и повторно тестируются) и редко меняются (они добавляются, но редко изменяются)
  • Внутренние API скрыты за общедоступными API и могут быть изменены без переписывания тестовых примеров, которые проверяют общедоступные API

Сноска: некоторые из моих «контрольных примеров» фактически реализованы в виде данных. Например, контрольные примеры для пользовательского интерфейса состоят из файлов данных, которые содержат различные пользовательские входные данные и соответствующие ожидаемые системные выходные данные. Тестирование системы означает наличие тестового кода, который считывает каждый файл данных, воспроизводит ввод в систему и утверждает, что он получает соответствующий ожидаемый вывод.

Хотя мне редко нужно менять тестовый код (поскольку публичные API-интерфейсы обычно добавляются, а не изменяются), я обнаруживаю, что иногда (например, два раза в неделю) необходимо изменять некоторые существующие файлы данных. Это может случиться, когда я изменяю системный вывод в лучшую сторону (то есть новая функциональность улучшает существующий вывод), что может привести к сбою существующего теста (поскольку код теста только пытается утверждать, что вывод не изменился). Для обработки этих случаев я делаю следующее:

  • Перезапустите автоматизированный набор тестов со специальным флагом времени выполнения, который говорит ему не утверждать вывод, а вместо этого захватывать новый вывод в новый каталог
  • Используйте инструмент визуального сравнения, чтобы увидеть, какие файлы выходных данных (т.е. какие тестовые примеры) изменились, и убедиться, что эти изменения хороши и, как и ожидалось, с учетом новой функциональности
  • Обновите существующие тесты, скопировав новые выходные файлы из нового каталога в каталог, из которого выполняются тестовые случаи (перезаписывая старые тесты)

Сноска: под "компонентом" я подразумеваю что-то вроде "одна DLL" или "одна сборка" ... что-то достаточно большое, чтобы его можно было увидеть в архитектуре или диаграмме развертывания системы, часто реализованной с использованием десятков или 100 классы, и с открытым API, который состоит только из 1 или нескольких интерфейсов ... что-то, что может быть назначено одной команде разработчиков (где другой компонент назначен другой команде), и поэтому будет в соответствии с Закон Конвея , имеющий относительно стабильный публичный API.


Сноска: статья Объектно-ориентированное тестирование: миф и реальность говорит,

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

Реальность: ОО структура имеет значение, часть II. Многие исследования показали, что тесты черного ящика считаются мучительно тщательный разработчиками только упражнение от одной трети до половины из утверждений (не говоря уже о путях или штаты) в реализации под тестовое задание. Есть три причины этот. Во-первых, входы или состояния выбрано обычно упражнение нормальное пути, но не заставляйте все возможное дорожки / состояния. Во-вторых, черный ящик одно только тестирование не может выявить сюрпризов. Предположим, мы проверили все указанное поведение системы под тестом. Чтобы быть уверенным, есть нет неопределенного поведения, нам нужно знать, есть ли какие-либо части системы не использовался черным ящиком тестирование. Единственный способ это информацию можно получить по коду измерительные приборы. В-третьих, это часто трудно осуществить исключение и обработка ошибок без проверки исходный код.

Я должен добавить, что я выполняю функциональное тестирование whitebox: я вижу код (в реализации) и пишу функциональные тесты (которые управляют общедоступным API) для проверки различных ветвей кода (подробности реализации функции).

Ответы [ 15 ]

0 голосов
/ 26 мая 2009

Аксиома: каждый программист должен протестировать свой собственный код

Я не думаю, что это универсально верно.

В криптографии есть известная поговорка: «Легко создать шифр, настолько безопасный, что вы не знаете, как его взломать самостоятельно».

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

Ваша уверенность сделает вас менее бдительным тестером. Тот, кто не поделится вашим опытом с кодом, не будет иметь проблемы.

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

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

Я думаю, что главный вопрос заключается в следующем: какая методология лучше всего подходит для поиска ошибок в программном обеспечении? Недавно я смотрел видео (без ссылки, извините), в котором говорится, что рандомизированное тестирование дешевле и эффективнее, чем тесты, созданные человеком.

0 голосов
/ 13 мая 2009

[Ответ на мой вопрос]

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

  • Аксиома: каждый программист должен проверить свой собственный код

  • Поэтому: если программист пишет и доставляет один «модуль», то он также должен был проверить этот модуль, вполне возможно, написав «модульный тест»

  • Следствие: если один программист пишет целый пакет, то программисту достаточно написать функциональные тесты всего пакета (нет необходимости писать «модульные» тесты модулей внутри пакета, поскольку эти модули детали реализации, к которым другие программисты не имеют прямого доступа / доступа).

Аналогично, практика создания «фиктивных» компонентов, с которыми вы можете проверить:

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

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

0 голосов
/ 13 мая 2009

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

0 голосов
/ 13 мая 2009

Я согласен, что охват кода в идеале должен составлять 100%. Это не обязательно означает, что 60 строк кода будут иметь 60 строк тестового кода, но каждый путь выполнения проверяется. Единственное, что раздражает больше, чем ошибка - это ошибка, которая еще не запущена.

Проверяя только открытый API, вы рискуете не тестировать все экземпляры внутренних классов. Я действительно констатирую очевидное, говоря это, но я думаю, что это следует упомянуть. Чем больше тестируется каждое поведение, тем легче распознать не только то, что оно нарушено, но и то, что оно нарушено.

0 голосов
/ 13 мая 2009

Я тоже лично тестирую защищенные части, потому что они являются "открытыми" для унаследованных типов ...

...