что такое тестирование методом белого ящика

Тестирование белого ящика

Что такое тестирование белого ящика?

WHITE BOX TESTING — это тестирование внутренней структуры, дизайна и кодирования программного решения. В этом типе тестирования код виден тестеру. Основное внимание уделяется проверке потока входных и выходных данных через приложение, улучшению дизайна и удобства использования, усилению безопасности. Тестирование белого ящика также известно как тестирование Clear Box, тестирование Open Box, структурное тестирование, тестирование прозрачного бокса, тестирование на основе кода и тестирование Glass Box. Это обычно выполняется разработчиками.

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

В этом уроке вы узнаете

Что вы проверяете в White Box Testing?

Тестирование белого ящика включает тестирование программного кода для следующего:

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

Нажмите здесь, если видео не доступно

Как вы проводите тестирование белого ящика?

ШАГ 1) ПОНИМАТЬ ИСТОЧНИК КОД

Первое, что часто делает тестер, — это изучает и понимает исходный код приложения. Поскольку тестирование белого ящика включает в себя тестирование внутренней работы приложения, тестировщик должен быть очень хорошо осведомлен в языках программирования, используемых в тестируемых приложениях. Кроме того, тестирующий должен быть хорошо осведомлен о методах безопасного кодирования. Безопасность часто является одной из основных задач тестирования программного обеспечения. Тестер должен уметь обнаруживать проблемы с безопасностью и предотвращать атаки хакеров и наивных пользователей, которые могут вводить вредоносный код в приложение как сознательно, так и неосознанно.

Шаг 2) СОЗДАТЬ ИСПЫТАТЕЛЬНЫЕ ДЕЛА И ИСПОЛНИТЬ

Пример тестирования WhiteBox

Рассмотрим следующий фрагмент кода

Целью тестирования WhiteBox является проверка всех ветвей решений, циклов, операторов в коде.

Чтобы выполнить утверждения в приведенном выше коде, тестовые случаи WhiteBox будут

Методы испытаний белой коробки

Основным методом тестирования Белого ящика является анализ покрытия кода. Анализ покрытия кода устраняет пробелы в наборе тестовых примеров. Он определяет области программы, которые не выполняются набором тестовых случаев. После выявления пробелов вы создаете контрольные примеры для проверки непроверенных частей кода, тем самым повышая качество программного продукта.

Доступны автоматизированные инструменты для анализа покрытия кода. Ниже приведены несколько методов анализа покрытия

Охват операторов: — Этот метод требует, чтобы каждое возможное утверждение в коде было проверено хотя бы один раз в процессе тестирования разработки программного обеспечения.

Покрытие ветвления — этот метод проверяет все возможные пути (если-еще и другие условные циклы) программного приложения.

Помимо вышесказанного, существует множество типов покрытия, таких как покрытие условий, покрытие нескольких условий, покрытие пути, покрытие функций и т. Д. Каждый метод имеет свои преимущества и пытается протестировать (охватить) все части программного кода. Используя покрытие Statement и Branch, вы обычно достигаете 80-90% покрытия кода, что является достаточным.

Чтобы узнать более подробно, обратитесь к этой статье https://www.guru99.com/code-coverage.html

Типы тестирования белого ящика

Тестирование белого ящика включает в себя несколько типов тестирования, используемых для оценки удобства использования приложения, блока кода или конкретного программного пакета. Там перечислены ниже —

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

Тестирование на утечки памяти : утечки памяти являются основными причинами медленной работы приложений. Специалист по обеспечению качества, имеющий опыт в обнаружении утечек памяти, необходим в тех случаях, когда у вас медленно работает программное приложение.

Помимо вышесказанного, несколько типов тестирования являются частью тестирования как черного ящика, так и белого ящика. Они перечислены ниже

Инструменты тестирования White Box

Ниже приведен список лучших инструментов тестирования белого ящика.

Преимущества тестирования белого ящика

Недостатки тестирования WhiteBox

Конечные заметки:

Источник

Тестирование белого ящика

что такое тестирование методом белого ящика. картинка что такое тестирование методом белого ящика. что такое тестирование методом белого ящика фото. что такое тестирование методом белого ящика видео. что такое тестирование методом белого ящика смотреть картинку онлайн. смотреть картинку что такое тестирование методом белого ящика.

Введение в тестирование белого ящика

Что такое тестирование белого ящика?

Тестирование белого ящика также называется тестированием кода, тестированием прозрачного бокса, тестированием открытого бокса и структурным тестированием. Основная идея этого подхода к тестированию программного обеспечения заключается в рассмотрении внутренней структуры и кода программы для ее тестирования.

При тестировании Белого ящика тестер может видеть весь код программы, и ему поручено проверить последовательность работы входов и выходов в программе. В отличие от тестирования «черного ящика», которое в большей степени ориентировано на тестирование функциональности программы, White Box Testing занимается тестированием внутренних структур программы. Такой взгляд на программу позволяет нам работать над улучшением дизайна, удобства использования и повышением безопасности продукта.

Как вы можете догадаться, это называется тестированием белого или стеклянного ящика, потому что тестер может видеть код и другие части программы.

Что отличает тестирование белого ящика от тестирования черного ящика

Если в прошлом вы испытывали трудности при тестировании, я уверен, что вы сталкивались с тестированием Black Box. Самое большое различие между тестированием White Box и тестированием Black Box заключается в том, что в отличие от тестирования Black Box, которое проводится с точки зрения пользователя, тестирование White Box выполняется с точки зрения разработчика.

Другими словами, вместо того, чтобы смотреть на программу извне, подход White Box Testing видит внутренний код и тестирует его.

Как проводится тестирование белого ящика?

Мы можем разделить процесс тестирования белого ящика на два основных этапа.

1. Понимание предоставленного кода

Безопасность является одним из важных аспектов тестирования White Box, поэтому тестировщик также должен уметь применять методы безопасного кодирования.

2. Создание тестовых случаев и их выполнение

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

Это также можно сделать в ручном тестировании, которое включает в себя метод проб и ошибок Тестировщики также могут использовать некоторые инструменты автоматического тестирования, такие как JUnit и NUnit.

Пример тестирования белого ящика

Чтобы лучше понять концепцию White Box Testing, взгляните на код ниже:

print (int x, int y) (
int sum = x + y;
If ( sum > 0 )
Print ( «Positive», result )
Else
Print ( «Negative», result )
)

Как мы уже обсуждали ранее, целью White Box Testing является обход всех ветвей, циклов и операторов, присутствующих в коде. Учитывая это, мы можем сделать 2 теста, один, где оба входа положительны, а другой, где оба входа являются отрицательными целыми числами.

Методы испытаний белой коробки

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

Ниже приведены некоторые методы анализа покрытия:

Есть и другие методы тестирования, вот лишь некоторые из них:

READ A, B
IF (A == 0 || B == 0)
PRINT ‘0’

Как видите, здесь у нас есть 2 условия: A == 0 и B == 0. Теперь эти условия получают TRUE и FALSE в качестве значений. Одним из возможных примеров может быть:

READ A, B
IF (A == 0 || B == 0)
PRINT ‘0’

# TC1: A = 0, B = 0
# TC2: A = 0, B = 10
# TC3: A = 110, B = 0
# TC4: A = 110, B = 5

Следовательно. Нам требуется 4 теста для 2 условий.

Следовательно, если есть n условий, то нам потребуется 2 n тестовых случаев.

1. Простые циклы: для цикла, который прост по дизайну и имеет размер n, мы можем разработать несколько тестовых примеров, которые выполняют следующее:

2. Вложенные циклы: Для кода с вложенными циклами мы начинаем с самого внутреннего цикла, а затем идем наружу, пока не достигнем самого внешнего цикла.

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

преимущества

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

Недостатки

Инструменты тестирования White Box

Теперь, когда вы знакомы с преимуществами, недостатками и методами тестирования белого ящика, мы можем взглянуть на некоторые популярные инструменты, которые тестировщики могут использовать для тестирования белого ящика.

JSUnit.net

Это инструмент тестирования JavaScript. JSUnit является частью Junit и является модульным модулем тестирования с открытым исходным кодом, который можно использовать для тестирования White Box. JSUnit является полностью открытым исходным кодом в соответствии с GNU Public License 2.0, что означает, что даже для коммерческого использования разработчик не должен платить никаких лицензионных сборов.

CppUnit

Как и JSUnit, CppUnit также считается частью JUnit. Инструмент может выводить в виде простого текста или в формате XML, в зависимости от потребностей тестировщика, и может создавать модульные тесты со своими собственными классами. CppUnit распространяется по лицензии LGPL.

Veracode

NUnit

JUnit

CSUnit

Вывод

Тестирование занимает очень важное место в процессе разработки программного обеспечения, и White Box Testing является ценным подходом к его выполнению. Хотя этот подход к тестированию может быть дорогостоящим и длительным, White Box Testing остается единственным способом убедиться, что все части кода были охвачены в процессе тестирования.

Рекомендуемые статьи

Источник

Тестирование белого ящика и черного ящика

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

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

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

Тестирование чёрного ящика или поведенческое тестирование

Тестирование чёрного ящика или поведенческое тестирование — стратегия (метод) тестирования функционального поведения объекта (программы, системы) с точки зрения внешнего мира, при котором не используется знание о внутреннем устройстве тестируемого объекта. Под стратегией понимаются систематические методы отбора и создания тестов для тестового набора. Стратегия поведенческого теста исходит из технических требований и их спецификаций.

Тестирование по стратегии белого ящика

Тестирование по стратегии белого ящика — тестирование кода на предмет логики работы программы и корректности её работы с точки зрения компилятора того языка, на котором она писалась.Тестирование по стратегии белого ящика, также называемое техникой тестирования, управляемой логикой программы, позволяет проверить
внутреннюю структуру программы. Исходя из этой стратегии тестировщик получает тестовые данные путем анализа логики работы программы.

Техника Белого ящика включает в себя следующие методы тестирования:
1. покрытие решений
2. покрытие условий
3. покрытие решений и условий
4. комбинаторное покрытие условий

Источник

Тестирование белого ящика

Разработка программ высокого качества подразумевает, что программа и её части подвергаются тестированию. Классическое модульное (unit) тестирование подразумевает разбиение большой программы на маленькие блоки, удобные для тестов. Либо, если разработка тестов происходит параллельно с разработкой кода или тесты разрабатываются до программы (TDD — test driven development), то программа изначально разрабатыватся небольшими блоками, подходящими под требования тестов.

Одной из разновидностей модульного тестирования можно считать propery-based testing (такой подход реализован, например, в библиотеках QuickCheck, ScalaCheck). Этот подход основан на нахождении универсальных свойств, которые должны быть справедливы для любых входных данных. Например, сериализация с последующей десериализацией должна давать такой же объект. Или, повторная сортировка не должна менять порядок элементов в списке. Для проверки таких универсальных свойств в вышеупомянутых библиотеках поддерживается механизм генерации случайных входных данных. Особенно хорошо такой подход работает для программ, основанных на математических законах, которые служат универсальными свойствами, справедливыми для широкого класса программ. Есть даже библиотека готовых математических свойств — discipline — позволяющая проверить выполнение этих свойств в новых программах (хороший пример повторного использования тестов).

Иногда оказывается, что необходимо протестировать сложную программу, не имея возможности разобрать её на независимо проверяемые части. В таком случае тестируемая программа представляет собой черный белый ящик (белый — потому что мы имеем возможность изучать внутреннее устройство программы).

Под катом описаны несколько подходов к тестированию сложных программ с одним входом с разной степенью сложности (вовлеченности) и разной степенью покрытия.

*В этой статье мы предполагаем, что тестируемую программу можно представить в виде чистой функции без внутреннего состояния. (Некоторые соображения, приведённые далее, можно применять и в том случае, если внутреннее состояние присутствует, но есть возможность сброса этого состояния к фиксированному значению.)

Тестовый стенд (test bench)

Прежде всего, так как тестируется всего одна функция, код вызова которой всегда одинаков, то у нас нет необходимости создавать отдельные unit test’ы. Все такие тесты были бы одинаковыми с точностью до входных данных и проверок. Вполне достаточно в цикле передавать исходные данные ( input ) и проверять результаты ( expectedOutput ). Чтобы в случае обнаружения ошибки можно было идентифицировать проблемный набор тестовых данных, все тестовые данные надо снабдить меткой ( label ). Таким образом, один набор тестовых данных можно представить в виде тройки:

Результат одного прогона можно представить в виде TestCaseResult :

Чтобы упростить прогон всех тестовых данных через тестируемую программу, можно использовать вспомогательную функцию, которая будет вызывать программу для каждого входного значения:

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

Для удобства можно отформатировать результаты тестирования

и выводить отчёт только в случае ошибок:

Подготовка входных данных

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

При работе со вложенными неизменяемыми структурами данных большим подспорьем являются линзы, например, из библиотеки Monocle:

Линзы позволяют элегантно «модифицировать» глубоко вложенные части структур данных: Каждая линза представляет собой getter и setter для одного свойства. Линзы можно соединять и получать линзы, «фокусирующиеся» на следующем уровне.

Использование DSL для представления изменений

Далее будем рассматривать формирование тестовых данных путём внесения изменений в некоторый исходный входной объект. Обычно для получения нужного нам тестового объекта требуется внести несколько изменений. При этом весьма полезно в текстовое описание TestCase’а включить перечень изменений:

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

Чтобы текстовый перечень изменений не расходился с фактическими изменениями, необходимо следовать принципу «единой версии правды». (Если одна и та же информация требуется/используется в нескольких точках, то следует иметь единственный первичный источник уникальной информации, а во все остальные точки использования информация должны распространяться автоматически, с необходимыми преобразованиями. Если этот принцип нарушать, и копировать информацию вручную, то неизбежно расхождение версий информации в разных точках. То есть в описании тестовых данных мы увидем одно, а в тестовые данных — другое. Например, копируя изменение field2 = «456» и корректируя его в field3 = «789» мы можем случайно забыть исправить описание. В итоге описание будет отражать только два изменения из трёх.)

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

Чтобы иметь возможность оперировать изменениями, необходимо иметь их структурированную модель. Модель должна быть достаточно выразительной, чтобы описывать все интересующие нас изменения. Частью этой модели, например, будет адресация полей объектов, константы, операции присваивания.

Модель изменений должна позволять решать следующие задачи:

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

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

Приведём пример программы, записанной с использованием DSL:

Для представления изменений необходим набор классов того же плана, что и вышеприведённый класс SetProperty :

Интерпретатор языка изменений представляет собой обычный рекурсивный вычислитель выражений, основанный на PatternMatching’е. Что-то наподобие:

Для непосредственного оперирования свойствами объектов необходимо для каждого свойства, используемого в модели изменений, задать getter и setter. Этого можно достичь, заполнив отображение ( Map ) между онтологическими свойствами и соответствующими им линзами.

Такой подход в целом работает, и действительно позволяет описывать изменения один раз, однако постепенно появляется потребность представлять всё более сложные изменения и модель изменений несколько разрастается. Например, если необходимо изменить какое-то свойство с использованием значения другого свойства того же объекта (например, field1 = field2 + 1 ), то возникает необходимость в поддержки переменных на уровне DSL. А если изменение свойства нетривиально, то на уровне DSL потребуется поддержка арифметических выражений и функций.

Тестирование ветвей

Тестируемый код может быть линейным, и тогда нам по большому счёту достаточно одного набора тестовых данных, чтобы понять, работает ли он. В случае наличия ветвления ( if-then-else ), необходимо запускать белый ящик как минимум дважды с разными входными данными, чтобы были исполнены обе ветки. Количество наборов входных данных, достаточных для покрытия всех ветвей, по-видимому, численно равно цикломатической сложности кода с ветвлениями.

Как сформировать все наборы входных данных? Так как мы имеем дело с белым ящиком, то мы можем вычленить условия ветвления и дважды модифицировать входной объект так, чтобы в одном случае выполнялась одна ветвь, в другом случае — другая. Рассмотрим пример:

Имея такое условие, мы можем сформировать два тестовых случая:

(В случае, если один из тестовых сценариев невозможно создать, то можно считать, что обнаружен мертвый код, и условие вместе с соответствующей веткой можно спокойно удалить.)

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

Рассмотрим подробнее механизм, позволяющий сформировать все возможные перечни изменений, обеспечивающие полное покрытие всех ветвлений. Для того, чтобы использовать перечень изменений при тестировании, нам надо все изменения объединить в один объект, который мы подадим на вход тестируемого кода, то есть требуется поддержка композиции. Для этого можно либо воспользоваться вышеприведённым DSL для моделирования изменений, и тогда достаточно простого списка изменений, либо представить одно изменение в виде функции модификации T => T :

тогда цепочка изменений будет представлять собой просто композицию функций:

или, для списка изменений:

Чтобы компактно записать все изменения, соответствующие всем возможным ветвлениям, можно использовать DSL следующего уровня абстракции, моделирующий структуру тестируемого белого ящика:

Здесь коллекция tests содержит агрегированные изменения, соответствующие всем возможным комбинациям ветвей. Параметр типа String будет содержать все названия условий и все описания изменений, из которых сформирована агрегированная функция изменений. А второй элемент пары типа T => T — как раз агрегированная функция изменений, полученная в результате композиции отдельных изменений.

Чтобы получить изменённые объекты, надо все агрегированные функции изменений применить к baseline-объекту:

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

Исходя из структуры модели тестируемого кода в форме дерева, перечни изменений будут представлять собой пути от корня к листам этого дерева. Тем самым значительная часть изменений будет дублироваться. Можно избавиться от этого дублирования, используя вариант DSL, при котором изменения непосредственно применяются к baseline-объекту по мере продвижения по ветвям. В этом случае будет производиться несколько меньше лишних вычислений.

Автоматическое формирование тестовых данных

Так как мы имеем дело с белым ящиком, то можем видеть все ветвления. Это даёт возможность построения модели логики, содержащейся в белом ящике, и использования модели для генерации тестовых данных. В случае, если тестируемый код написан на Scala, можно, например, использовать scalameta для чтения кода, с последующем преобразованием в модель логики. Опять же, как и в рассмотренном ранее вопросе моделирования логики изменений, для нас затруднительно моделирование всех возможностей универсального языка. Далее будем предполагать, что тестируемый код реализован с использованием ограниченного подмножества языка, либо на другом языке или DSL, который изначально ограничен. Это позволяет сосредоточиться на тех аспектах языка, которые представляют для нас интерес.

Рассмотрим пример кода, содержащего единственное ветвление:

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

Трудно обратимые функции

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

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

Дополнительный параметр позволяет обеспечить выполнение кода внутри ветки, но, очевидно, может привести к фактически некорректным результатам. То есть тестируемая программа будет выдавать результаты, которые никогда не могут наблюдаться в реальности. Тем не менее, проверка части кода, которая иначе нам недоступна, всё равно полезна и может рассматриваться как разновидность модульного тестирования. Ведь и при модульном тестировании подфункция вызывается с такими аргументами, которые, возможно, никогда не будут использоваться в программе.

При таких манипуляциях мы заменяем (подменяем) объект тестирования. Тем не менее, в каком-то смысле новая построенная программа включает логику прежней программы. Действительно, если в качестве значений новых искуственных параметров взять результаты вычисления функций, которые мы заменили на параметры, то программа выдаст те же самые результаты. По-видимому, тестирование изменённой программы по-прежнему может представлять интерес. Надо лишь помнить, при каких условиях изменённая программа будет вести себя также, как исходная.

Зависимые условия

Если при формировании уточняющих множеств мы обнаружим, что одно из подмножеств пусто, то это означает, что условие всегда будет принимать фиксированное значение true или false вне зависимости от входных значений. Поэтому соответствующая ветка, которая никогда не вызывается, является «мертвым кодом» и может быть удалена из кода вместе с условием.

Связанные параметры

Рассмотрим случай, когда условие ветвления основано на двух полях объекта, также связанных условиями:

Символьное выполнение

Для того, чтобы собрать все условия, порождающие результат по какой-либо из ветвей, можно воспользоваться «cимвольным выполнением» (Symbolic Execution, Символьное выполнение программ), суть которого заключается в следующем. Входные данные принимаются равными некоторым символьным значениям ( field1 = field1_initial_value ). Затем над символьными значениями производятся все манипуляции, описанные в тестируемом коде. Все манипуляции выполняются в символьном же виде:

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

Тестирование циклов и рекурсивных функций

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

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

Воспользуемся такой конструкцией как Y-комбинатор («комбинатор неподвижной точки», stackoverflow:What is a Y-combinator? (2-ой ответ), habr: Получение Y-комбинатора в 7 простых шагов). Комбинатор позволяет реализовать рекурсию в языках, которые рекурсию в чистом виде не поддерживают. (Сам комбинатор является рекурсивным, поэтому должен быть реализован на языке, поддерживающем рекурсию.) Работает он следующим образом. Из рекурсивной функции удаляются все рекурсивные вызовы и заменяются на вызовы функции, которая передаётся в качестве дополнительного аргумента. Такая переработанная функция уже не будет являться рекурсивной, а служит только «заготовкой» для получения целевой функции. Y-комбинатор превращает такую «заготовку рекурсивной функции» в полноценную рекурсивную функцию (передавая в качестве аргумента собственное продолжение).

В случае общей рекурсии рекурсивный вызов возвращает результат, который затем используется. В этом случае вышеприведённый подход напрямую не работает. Можно попробовать применить подход, аналогичный тому, что мы использовали для вызовов трудно обратимых функций. А именно, заменим каждый рекурсивный вызов на новый параметр. Значение этих параметров можно будет генерировать как обычно, исходя из условий ветвлений, в которых эти параметры используются. Как и в случае с заменой вызовов функций на параметры, результаты, которые мы будем получать, могут отличаться от результатов, которые мы можем получить в действительности. Совпадение будет достигаться в том случае, если значение параметра совпадает со значением рекурсивной функции. Такой подход позволяет нам протестировать шаги, выполняемые после рекурсивного вызова.

Смысл тестирования белого ящика

При определённом усердии можно добиться того, что тесты, написанные вручную или сгенерированные автоматически, будут покрывать все ветви тестируемого кода, то есть обеспечат 100% покрытие. Тем самым мы сможем с уверенностью сказать, что белый ящик делает то, что он делает. Хм. Секундочку. А в чём, собственно, смысл такого тестирования, спросит внимательный читатель? Ведь для любого содержимого белого ящика будут построены тесты, которые только лишь подтверждают, что белый ящик работает каким-то определённым образом.

В некоторых случаях такой набор тестов всё же может иметь смысл:

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

Заключение

Тестирование белого ящика смещает акцент с вопроса «что должен делать код» на «что фактически делает код». Иными словами, вместо использования более высокого уровня абстракции, формирования тестов на основе спецификации, используется точно тот же уровень абстракции, что и при реализации кода. Мы можем получить хорошие результаты в плане покрытия кода, но при этом такое тестирование имеет смысл в ограниченном наборе случаев.

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

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

Благодарности

Хотелось бы поблагодарить @mneychev за терпение и неоценимую помощь при подготовке статьи.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *