Рекомендация: Идеи тестов для булевских выражений и граничных условий
Идеи тестов опираются на вероятные сбои программного обеспечения и способы обнаружения этих сбоев. Эта рекомендация посвящена разработке идей тестов для булевских выражений и граничных условий.
Взаимосвязи
Связанные элементы
Основное описание

Введение

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

Булевские выражения

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

 if (publicIsClear || technicianClear) {
   bomb.detonate(); }

Этот код - ошибочный. || должен быть заменен на &&. Результаты этой ошибки будут трагические. Бомба будет взорвана не только в том случае, когда обеспечена безопасность и персонала, и техника, но и когда обеспечена безопасность одного из этих объектов.

Какой тест позволит это обнаружить?

Пусть тест проводится в ситуации, когда обеспечена безопасность и персонала, и техника. Бомба будет взорвана. Но - и это важно - правильный код с && сделает то же самое. Поэтому тест в отношении этой ошибки будет бесполезен.

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

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

publicIsClear

technicianClear

тестируемый код...

правильный код...

 

true

true

взрыв

взрыв

тест бесполезен (не выявляет эту ошибку)

true

false

взрыв

нет взрыва

полезный тест

false

true

взрыв

нет взрыва

полезный тест

false

false

нет взрыва

нет взрыва

тест бесполезен (не выявляет эту ошибку)


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

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

Обнаруженные ошибки

Ошибки, которые могут остаться невыявленными

Неверный оператор: a || b вместо && b Неверная переменная: a&&b&&c вместо a&& x&&d
Неверное или упущенное отрицание a||b вместо !a||b, или ! a||b вместо a||b Неполное выражение: a&&b вместо a&&b&&c
Неверные скобки в выражении: a&&b||c вместо a&&(b||c) Выражения, содержащие несколько ошибок в левом столбце
Слишком сложное выражение: a&&b&&c вместо a&&b
Эта ошибка маловероятна, но тесты, позволяющие ее найти, будут использоваться и в других случаях.
 

Как применяются эти идеи? Предположим, у нас есть булевское выражение a&&!b. Составим таблицу истинности:

a

b

a&&!b
(текущий код)

возможно, следует заменить на
a||!b

возможно, следует заменить на
!a&&!b

возможно, следует заменить на
a&&b

...

true

true

false

true

false

true

...

true

false

true

true

false

false

...

false

true

false

false

false

false

...

false

false

false

true

true

false

...


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

Конечно, построить полную универсальную таблицу невозможно. но это и не требуется. Несложно будет запомнить только обязательные варианты для простых выражений. Они описаны ниже. Более сложные выражения, такие как A&&B||C, описаны в разделе Идеи тестов для смешанных случаев AND и OR, где перечислены идеи тестов для выражений с двумя или более операторами. Для еще более сложных выражений идеи тестов может генерировать программа.

Таблицы для простых булевских выражений

Выражение A&&B тестируется следующим образом:

A

B

true

true

true

false

false

true


Выражение A||B тестируется следующим образом:

A

B

true

false

false

true

false

false


Выражение A1 && A2 && ... && An тестируется следующим образом:

A1, A2, ..., и An все равны true

A1 равен false, все остальные - true

A2 равен false, все остальные - true

...

An равен false, все остальные - true


Выражение A1 || A2 || ... || An тестируется следующим образом:

A1, A2, ..., и An все равны false

A1 равен true, все остальные - false

A2 равен true, все остальные - false

...

An равен true, все остальные - false


Выражение A тестируется следующим образом:

A

true

false


При тестировании a&&!b можно применить эту таблицу, обратить значение b, и получить следующий список идей тестов:

  • A true, B false
  • A true, B true
  • A false, B false

Выражение отношения

Вот еще один пример ошибочного кода:

 if (finished < required) {
   siren.sound(); }

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

finished

required

тестируемый код...

правильный код...

1

5

срабатывает сирена

срабатывает сирена

5

5

сирена молчит

срабатывает сирена

5

1

сирена молчит

сирена молчит


Вообще говоря, эта ошибка выявляется, когда finished=required. Из анализа для вероятных ошибок вытекают следующие правила для идей тестов:

Выражение A<B или A>=B, следует тестировать со следующими вариантами:

A=B

A чуть меньше, чем B


Выражение A>B или A<=B, следует тестировать со следующими вариантами:

A=B

A чуть больше, чем B


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

Правила для сочетаний булевских выражений и выражений отношения

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

 if (finished < required) {
   siren.sound(); }

Правила для выражений отношения приводят к следующим идеям для тестов:

  1. finished равно required
  2. finished чуть меньше, чем required

Правила для булевских выражений приводят к следующим идеям для тестов:

  1. finished < required должно быть равным true
  2. finished < required должно быть равным false

Если finished чуть меньше, чем required, finished < required равно true, поэтому второе выражение будет излишним.

Также необязательно записывать это выражение, если оно равно false: finished равно required, , finished < required .

Если выражение отношения не содержит булевских операторов && и ||), то факт того, что это - булевское выражение, можно игнорировать.

Сочетание булевских операторов и операторов отношения усложняет анализ:

 if (count<5 || always) {
   siren.sound(); }

Для выражения отношения имеем:

  • count чуть меньше 5
  • count равно 5

Для булевского выражения имеем:

  • count<5 равно true, always равно false
  • count<5 равно false, always равно true
  • count<5 равно false, always равно false

Из этого можно вывести три отдельных идеи теста. Считаем, что count - это целое число.

  1. count=4, always равно false
  2. count=5, always равно true
  3. count=5, always равно false

Обратите внимание, что count=5 встречается дважды. Не лучше ли будет использовать другое значение вместо 5 для count ? Может быть, один раз протестировать 5, а другой раз - с другим значением, чтобы результатом было false? Пример: count<5 Это можно, но небезопасно. Легко допустить ошибку. Предположим, мы тестируем так:

  1. count=4, always равно false
  2. count=5, always равно true
  3. count<5 false, always равно false

Допустим, что сбой может быть выявлен только со следующим значением: count=5. Это означает, что значение 5 приведет к тому, что выражение будет равно "false". count<5, в то время как правильный код должен давать true. Однако это значение сравнивается (OR) со значением always, равным true. Тем самым все выражение будет истинным, даже если один из его участников ложен. Ошибка останется невыявленной.

Ошибка будет выявлена другим вариантом с count=5.

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

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

Идеи тестов без кода

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

В некоторых артефактах проекта, таких как диаграммы состояний и диаграммы последовательностей, булевские выражения применяются как сторожевые условия. Эти варианты являются прямыми кандидатами для добавления в список идей тестов артефакта. See См. Рекомендации по рабочему продукту: идеи тестов для диаграмм конечных состояний и операций.

Самый сложный случай - когда булевские выражения заданы неявно. Часто это имеет место в описаниях API. Пример - рассмотрим следующий метод:

 List matchList(Directory d1, Directory d1,
                FilenameFilter excluder);

Описание поведения этого метода могло бы быть следующим:

Возвращает список с полными путями ко всем файлам в обоих каталогах, включая подкаталоги. [...] Файлы, имена которых совпадают с excluder , исключаются из списка. Параметр excluder применяется только для самих каталогов, но не для подкаталогов.

Здесь нет слов "и", "или". Но когда файл включается в список? Когда он есть в первом каталоге и во втором каталоге и либо в подкаталоге или не исключен параметром excluder. То есть:

 if (appearsInFirst && appearsInSecond && (inLowerLevel || !excluded)) {  
   add to list }

Это выражение можно протестировать согласно следующей таблице:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

false

false

false

true

false

false


Общим способом выявления неявных булевских выражений в тексте будет составление списка описанных действий (например, "возвращает соответствующее имя"). Затем следует оставить булевское выражение, описывающее возможные действия. Для всех таких выражений можно придумать тесты.

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

найти совпадение:
имеет место, когда файл с таким именем есть и в первом каталоге, и во втором каталоге
отфильтровать совпадения:
имеет место, когда соответствующие файлы расположены в каталоге верхнего уровня и имя совпадает с excluder

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

Во втором случае будут созданы два набора идей тестов.

идеи тестов для поиска совпадения:

  • файл есть в первом каталоге, файл есть во втором каталоге (true, true)
  • файл есть в первом каталоге, файла нет во втором каталоге (true, false)
  • файла нет в первом каталоге, файл есть во втором каталоге (false, true)

идеи тестов для фильтрации обнаруженных совпадений:

  • соответствующие файлы расположены в каталоге верхнего уровня, имя совпадает с excluder (true, true)
  • соответствующие файлы расположены в каталоге верхнего уровня, имя не совпадает с excluder (true, false)
  • соответствующие файлы расположены в подкаталоге каталога верхнего уровня, имя совпадает с excluder (false, true)

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

файл есть в первом каталоге

файл есть во втором каталоге

верхний каталог

совпадает с excluder

true

true

true

true

true

true

true

false

true

true

false

true


Две из идей тестов поиска совпадения не входят в эту таблицу. Добавим их:

файл есть в первом каталоге

файл есть во втором каталоге

верхний каталог

совпадает с excluder

true

true

true

true

true

true

true

false

true

true

false

true

true

false

-

-

false

true

-

-


Пустые ячейки указывают на случаи, которые нас не интересуют.

Теперь эта таблица очень похожа на ту, что мы имели ранее. Эта схожесть еще более усилится, если будет использоваться одинаковая терминология. В первой таблице есть столбец "inLower", во второй - "верхний каталог". Одно можно обратить в другое. После этого вторая таблица примет следующий вид:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

-

-

false

true

-

-


Первые три строки совпадают с тем, что мы имели ранее. Последние два отличаются только тем, что не указаны значения. Это обусловлено тем, как написан код. В первом случае предполагалось сложное булевское выражение:

 if (appearsInFirst && appearsInSecond && (inLowerLevel || !excluded)) {  
   add to list }

Во втором - вложенное булевское выражение:

 if (appearsInFirst && appearsInSecond) {
   // совпадение 
   if (inTopLevel && excluded) {
     // отфильтровать

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

  1. В первой реализации возможна ошибка в расстановке скобок. Скобки вокруг || поставлены верно или неверно? Поскольку во второй реализации скобок нет и нет ||, ошибка исключена.
  2. В требованиях к тесту первой реализации необходимо указать проверку того, не следует ли заменить выражение && на ||. Во второй реализации явный && заменен на неявный &&. Ошибка замены || на && исключена. Может быть ошибка в том, как реализована вложенность, но эта методика к ней не относится.