Модульное тестирование внутренних компонентов


14

В какой степени вы тестируете внутренние / частные компоненты класса / модуля / пакета / и т. Д.? Вы тестируете их вообще или просто тестируете интерфейс с внешним миром? Примером этих внутренних являются частные методы.

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

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

Я могу придумать несколько аргументов:

Плюсы :

  1. Больше тестирования всегда лучше, и это может помочь увеличить охват кода
  2. Некоторым внутренним компонентам может быть сложно дать определенные входные данные (например, крайние случаи), предоставляя входные данные для внешнего интерфейса
  3. Более четкое тестирование. Если внутренний компонент имеет (исправленную) ошибку, тестовый пример для этого компонента дает понять, что ошибка была в этом конкретном компоненте

Минусы :

  1. Рефакторинг становится слишком болезненным и отнимает много времени. Чтобы что-то изменить, нужно переписать юнит-тесты, даже если пользователи внешнего интерфейса не пострадали
  2. Некоторые языки и среды тестирования не позволяют этого

Каковы ваши мнения?


возможное дублирование или значительное совпадение с: programmers.stackexchange.com/questions/10832/…
azheglov

Ответы:


8

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

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

Замечания:

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

  • ничто не будет излишне обнародовано, т.е. контракт модуля будет сохранен, а инкапсуляция сохранена.

[Обновить]

Чтобы протестировать некоторую умную внутреннюю логику в тех случаях, когда трудно перевести внутренние части объекта (я имею в виду элементы, а не импортированные в частном порядке модули / пакеты) в надлежащее состояние, просто передав их входные данные через открытый интерфейс объекта:

  • просто получите тестовый код с доступом друга (в терминах C ++) или пакета (Java) к внутренностям, фактически устанавливая состояние изнутри и тестируя поведение по своему усмотрению.

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

и макет списка, кажется, немного сломан; (
mlvljr

1
Хороший ответ. В .NET вы можете использовать этот [assembly: InternalsVisibleTo("MyUnitTestAssembly")]атрибут AssemblyInfo.csдля тестирования внутренних компонентов. Это похоже на обман, хотя.
Никто

@ rmx Если что-то удовлетворяет всем необходимым критериям, это не обман, даже если есть что-то общее с реальными читами. Но тема меж / внутримодульного доступа действительно немного придумана в современных основных языках.
mlvljr

2

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

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

Этот вид тестирования ближе к тому, что называется «функциональными тестами», но устраняет необходимость менять тесты каждый раз, когда вы слегка касаетесь реализации.


2

Смотря как :-). Если вы придерживаетесь подхода BDD (Behavior Driven Development) или ATDD (Acceptance Test Driven Development), тогда тестирование общедоступного интерфейса - это хорошо (если вы тестируете его всесторонне с различными входными данными. на самом деле важно.

Однако если вы хотите, чтобы часть этого алгоритма выполнялась в течение определенного периода времени или вдоль определенной кривой bigO (например, nlogn), тогда да, тестирование отдельных частей имеет значение. Некоторые назвали бы это более традиционным подходом TDD / Unit Test.

Как и все, YMMV


1

Разделить его на несколько частей с функциональным значением, например ParseQuotedString(), ParseExpression(), ParseStatement(), ParseFile()и сделать их всех общественности. Какова вероятность того, что ваш синтаксис так сильно изменится, что он станет неактуальным?


1
этот подход легко приводит к более слабой инкапсуляции и большим и трудным для использования / понимания интерфейсам.
Сара
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.