Лекции. Тестирование ПО (all in one) (1186159), страница 13
Текст из файла (страница 13)
Однако тесты с входными данными 0, 2 и –2 дадут 100% покрытия строк и необнаружат никаких ошибок.1234567891011121314String classifier(int n){StringBuffer s = new StringBuffer();if(n == 0)return "ноль";if(n%2 == 0)s.append("четное ");if(n < 0)s.append("отрицательное");elses.append("положительное");151617return s.toString();}Проблема здесь в том, что определенный код ошибочно пропущен, а покрытиеинструкций, естественно, не гарантирует обнаружения пропущенных инструкций, посколькуоно вычисляется только по имеющимся. Чтобы решить эту проблему, используют покрытиеветвей.Для определения ветвей нужно рассмотреть граф потока управления программы.
Графпотока управления для приведенного выше примера изображен ниже. Для пояснений ублоков кода, которые всегда выполняются в одной последовательности, помещенысоответствующие инструкции. Каждый условный оператор (также как и оператор цикла иливыбора) имеет несколько ребер графа, ведущих из него, в соответствии с возможным ходомвыполнения инструкций, то есть определяет разветвление потока управления. Каждое ребро,выходящее из вершины графа, из которой выходят и другие ребра, называется ветвью.Условному оператору соответствуют два выходящих из его вершины ребра, операторупроверки условия цикла — тоже два (выйти из цикла или нет), а оператору выбора можетсоответствовать много выходящих ребер — по числу указанных вариантов значенийвыражения, по которому осуществляется выбор.StringBuffer s = new StringBuffer();Aдаn == 0нетBCданетn%2 == 0Dreturn "ноль";s.append("четное ");Eдаn < 0нетFs.append("отрицательное");s.append("положительное");return s.toString();Покрыть ветвь означает обеспечить такой ход выполнения инструкций, что послеинструкции ветвления, которая является начальной вершиной этой ветви, выполнитсяинструкция в концевой вершине ветви.
Недостижимой считается ветвь, которая не можетбыть покрыта ни при каком выполнении кода. Метрика покрытия ветвей (branch coverageили decision coverage) вычисляется аналогично покрытию инструкций — это доля покрытыхв ходе теста ветвей по отношению к общему количеству достижимых ветвей.В рассматриваемом примере имеется шесть ветвей — на схеме графа они помеченылатинскими буквами. Для каждой ветви, кроме D, есть соответствующий набор инструкций,который выполняется, когда покрывается эта ветвь.
В нашем случае концевая вершина ветвиD соответствует условному оператору, который выполнится и при покрытии другой ветви.Поэтому покрытие всех инструкций не гарантирует покрытия всех ветвей. Для полногопокрытия ветвей во втором примере можно использовать, например, тестовые данные 0, 1, 2и –2. Тест, использующий значение параметра 1, покажет, что для нечетных чиселсоответствующая пометка не выдается.Однако верно, что, если тесты дают 100% покрытия ветвей, они же всегда дадут и 100%покрытия инструкций.
Для величин покрытия, меньших 100%, никаких общих соотношениймежду покрытием инструкций и ветвей указать нельзя. С одной стороны, количествоинструкций может значительно превышать количество ветвей, с другой стороны, половинаветвей программы может не содержать инструкций, как в приведенном примере.
Поэтомупокрытие 95% инструкций может соответствовать только 5% покрытых ветвей и наоборот,покрыв 95% ветвей, можно покрыть только 5% инструкций.Но тот факт, что 100% покрытие ветвей автоматически означает 100% покрытияинструкций, уже достаточно важен. Он показывает, что метрика покрытия ветвей «негрубее» метрики покрытия инструкций, выделяет не меньше разнообразных ситуаций. Если100% покрытия по одной метрике тестового покрытия влечет 100% покрытия по другойметрике, говорят, что первая метрика сильнее или тоньше. Соответственно, вторая слабееили грубее первой.
Если одна метрика сильнее другой, а та тоже сильнее первой, ониэквивалентны. В этой ситуации все равно, какую из них использовать, если нас интересуеттолько полное, 100% покрытие. Метрика покрытия ветвей сильнее метрики покрытияинструкций, а обратное не верно. Поэтому при достижении 100% покрытия ветвейтестирование в общем случае оказывается более полным, чем при покрытии 100%инструкций.Чтобы покрыть все ветви в коде, вычисляющем наибольший общий делитель, тоже нужнодополнить набор тестов, покрывающий все инструкции, например, взять тестовые данные<0, 1>, <1, 0>, <1, –2>, <1, 2>.
Чтобы убедиться в этом, достаточно изучить графа потокауправления этого кода, представленный ниже. В нем вершины, соответствующие операторамветвления и выхода из тела функции помечены номерами строк, содержащих эти операторы.В этом примере две ветви — отрицательные ветви для условных операторов в строках 7 и 12— тоже не содержат соответствующих инструкций.34567101223Допустим, однако, что мы, исправив последний пример, нечаянно внесли в него еще однуошибку. Теперь нечетные числа помечаются как надо, но условие пометки отрицательныхчисел неправильное.12345678910111213141516171819String classifier(int n){StringBuffer s = new StringBuffer();if(n == 0)return "ноль";if(n%2 == 0)s.append("четное ");elses.append("нечетное ");if(n < 0 && n%2 == 0)s.append("отрицательное");elses.append("положительное");return s.toString();}Набор тестовых данных 0, 1, 2, –2, по-прежнему покрывающий 100% ветвей, необнаруживает такую ошибку.
Чтобы выявить ее, необходимо использовать тесты,покрывающие комбинации условий, встречающихся в операторах ветвления.Комбинации условий определяется следующим образом. Выделим условия всехветвлений в коде программы, определяемых условными операторами, операторами циклаили операторами выбора. Эти условия являются предикатами, составленными при помощилогических операций (отрицания «не», конъюнкции «и», дизъюнкции «или», равенства,неравенства или исключающего «или») из элементарных условий — логических формул,которые уже не разлагаются на составляющие логические формулы. Выделив всеэлементарные условия из ветвлений в коде заданной программы, мы можем составлятькомбинации из этих условий и их отрицаний, или, что то же самое, из их значений true и false(1 и 0).
В приведенном примере элементарными условиями являются предикаты (n == 0),(n%2 == 0) и (n < 0). Из трех условий и их отрицаний можно составить 8 комбинаций, однаконе все эти комбинации будут выполнимы. Так, не может быть одновременно выполнено (n== 0) и (n < 0), или (n == 0) и !(n%2 == 0). Выбросив все невыполнимые комбинации, мыполучим в данном примере только 5 комбинаций элементарных условий и их отрицаний —если (n == 0), значения остальных двух условий определяются однозначно, иначе дваоставшихся условия становятся независимыми и дают еще 4 комбинации.Номерn == 0n%2 == 0n<011102000300140105011Комбинация значений элементарных условий покрывается тестом, если во время еговыполнения эти условия в некоторый момент имеют в точности эти значения.Метрика покрытия комбинаций условий (multiple condition coverage) определяется какдоля покрытых текстами комбинаций значений элементарных условий, участвующих вусловиях ветвлений программы, по отношению к общему количеству выполнимыхкомбинаций значений элементарных условий.В приведенном выше примере набор тестовых данных 0, 1, 2, –2 не покрываеткомбинацию условий с номером 3 в таблице.
Покрыв эту ситуацию, например, с помощьюзначения параметра, равного –1, мы обнаружим внесенную в программу ошибку.Метрика покрытия комбинаций условий строго сильнее метрики покрытия ветвей,поскольку она сильнее, а при использовании составных предикатов в условиях ветвлений,как в нашем примере, становится неэквивалентной ей.В рассмотренном примере нам удалось достаточно быстро определить невыполнимыекомбинации, поскольку все условия зависят от одной целочисленной переменной n.
В общемслучае вопрос о выполнимости различных комбинаций достаточно непрост, посколькуэлементарные условия могут быть связаны неявно. Например, они могут использоватьглобальные переменные, значения которых подчиняются нетривиальным ограничениям,скажем, одна из переменных может представлять список каких-то объектов, а вторая —индекс одного из объектов в этом списке, либо –1, если список пуст.
В общем случаеопределить выполнимость комбинации значений произвольных формул оказываетсядостаточно сложно, поскольку для этого нужно знать смысл используемых в условияхпеременных и функций. Поэтому вычисление тестового покрытия по метрике покрытиякомбинаций условий в общем случае не автоматизируется.Другая трудность практического использования покрытия по этой метрике связана свозможным экспоненциальным ростом количества ситуаций, выделяемых для программыпри росте ее размера. Если в программе используется n операторов ветвления, условиекаждого из них элементарно и между этими условиями нет никаких связей, то возникаетвсего лишь 2n ветвей, но 2n возможных комбинаций условий.
Поэтому на практикеиспользуются метрики тестового покрытия, более сильные, чем покрытие ветвей, ноприводящие к меньшему числу различных ситуаций, чем покрытие комбинаций условий.Если при этом не учитывать покрытие ветвей, получится метрика покрытия условий(condition coverage), которая не сильнее метрики покрытия ветвей.
Для каждогоэлементарного условия определяется, сколько значений оно может принимать при всехвозможных сценариях выполнения программы. Обычно, если нет специфическихограничений, оно может принимать два значения. Но иногда встречаются постоянныеусловия, которые либо всегда равны true, либо всегда равны false, при всех выполненияхпрограммы.