Разработка тестов для алгоритма
Тестирование алгоритма — это проверка его работы на специально подобранных входных данных. Хороший набор тестов должен охватывать обычные, граничные и особые случаи, чтобы обнаружить ошибки в условиях, циклах, вычислениях и обработке данных.
Зачем нужны тесты
Алгоритм может давать правильный результат на нескольких примерах и всё же содержать ошибку. Например, условие может работать для положительных чисел, но ошибаться при нуле; цикл может пропускать последний элемент; формула может переполняться или обращаться к элементу за границами массива. Поэтому тесты подбирают не случайно, а так, чтобы проверить разные логические ситуации.
Тестовый пример — это конкретный набор входных данных вместе с ожидаемым результатом работы алгоритма. Тест считается пройденным, если фактический результат совпал с ожидаемым.
При подготовке теста нужно уметь самостоятельно определить правильный ответ. Для простых алгоритмов его вычисляют вручную, для сложных — используют независимый способ решения, таблицу, формулу или небольшой эталонный алгоритм. Нельзя получать ожидаемый ответ тем же ошибочным способом, который проверяется.
Тесты должны проверять не только разные значения входных данных, но и разные пути выполнения алгоритма: каждую ветвь условия, каждый важный случай завершения цикла и работу с минимальными и максимальными допустимыми значениями.
Какие тесты подбирать
Удобно составлять тесты по плану. Сначала определите ограничения задачи и части алгоритма, которые могут влиять на результат. Затем для каждой части выберите подходящие случаи.
- Обычный случай. Входные данные находятся примерно в середине допустимого диапазона и не имеют особых свойств.
- Граничные случаи. Проверяются минимальные и максимальные значения, нулевая длина, один элемент, пустая строка, граница диапазона.
- Случаи ветвления. Нужно отдельно проверить истинность и ложность каждого условия, включая равенство границе.
- Особые значения. Это одинаковые элементы, уже упорядоченные данные, обратный порядок, отрицательные числа, нули, повторяющиеся значения.
- Большой случай. Проверяется работа алгоритма при размере входа, близком к максимальному: число итераций, время и отсутствие переполнения.
Если в алгоритме есть условие \(x < a\), полезны тесты с \(x=a-1\), \(x=a\) и \(x=a+1\). Они показывают, правильно ли обработаны значения до границы, на границе и после неё.
Для циклов особенно важны случаи, когда цикл не выполняется ни разу, выполняется ровно один раз и выполняется много раз. Такой подход связан с методом трассировки цикла: по таблице значений переменных легко понять, какие итерации действительно происходят.
| Часть алгоритма | Что проверить | Пример теста |
|---|---|---|
| Ветвление | Обе ветви и равенство границе | \(x<10\): \(x=9\), \(x=10\), \(x=11\) |
| Цикл | 0, 1 и много итераций | Пустой список, один элемент, 100 элементов |
| Массив | Первый и последний индексы | Доступ к \(a[0]\) и \(a[n-1]\) |
| Формула | Ноль, знак и крайние значения | \(a=0\), отрицательное и максимальное \(a\) |
Разбиение входов на классы
Если возможных входов очень много, проверять каждый невозможно. Тогда используют тестирование классов эквивалентности. Все входные данные, на которых алгоритм должен вести себя одинаково, объединяют в класс. Из каждого класса выбирают один или несколько представителей.
Например, для условия «вывести YES, если число \(x\) принадлежит отрезку \([10;20]» можно выделить три класса:\)x<10\(,\)10\le x\le20\(и\)x>20\(. Но одного представителя недостаточно: отдельно проверяют\)x=10\(и\)x=20$, потому что ошибка часто возникает именно на границе.
Для условия \(n\le 100\) какой набор лучше всего проверяет границу?
Проверка ветвей и циклов
В алгоритме с несколькими условиями следует составить таблицу ситуаций. Для каждого простого условия отметьте, когда оно истинно и ложно. Если условия соединены операциями «и» или «или», полезны наборы, в которых меняется только одно условие, а остальные остаются прежними.
Для циклов проверьте начальное состояние и момент выхода. Если цикл имеет вид «пока \(i<n\)», важны значения \(n=0\), \(n=1\) и небольшое положительное \(n\). Если внутри цикла есть обращение к \(a[i+1]\), нужно убедиться, что последняя итерация не выходит за границу массива. При вложенных циклах полезно отдельно оценить число выполнений внутреннего тела; это рассматривается в анализе вложенных циклов.
Для цикла проверяйте как минимум: 0 итераций, 1 итерацию, несколько итераций и максимальное число итераций. Для ветвления проверяйте каждую ветвь и значения на границе условия.
Разобранный пример
Рассмотрим алгоритм, который подсчитывает количество элементов массива, больших среднего арифметического всех элементов. Требуется подобрать тесты, способные выявить ошибку в сравнении или в вычислении среднего.
1s := 0 2for i от 1 до n: 3 s := s + a[i] 4avg := s / n 5count := 0 6for i от 1 до n: 7 if a[i] > avg: 8 count := count + 1 9вывести count
Сначала выделим классы случаев: один элемент, одинаковые элементы, разные элементы, отрицательные значения и смешанные знаки. Затем вручную вычислим ожидаемый результат.
| Тест | Ожидаемый результат | Что проверяет |
|---|---|---|
| [7] | 0 | Одна итерация, деление на \(n=1\) |
| [4, 4, 4] | 0 | Равенство среднему |
| [1, 2, 6] | 1 | Разные значения |
| [-5, 0, 5] | 1 | Отрицательные и положительные числа |
| [1, 2] | 1 | Дробное среднее |
Если вместо строгого условия \(a[i]>avg\) ошибочно написать \(a[i]\ge avg\), тест [4, 4, 4] сразу даст неверный результат: ошибочный алгоритм выведет 3 вместо 0. Если среднее вычисляется как целое число при целочисленном делении, тест [1, 2] может также выявить ошибку.
Случайные и большие тесты
Случайное тестирование помогает быстро получить много разнообразных входов. Однако случайные данные могут не попасть на границу и не покрыть редкую ветвь, поэтому они дополняют, а не заменяют специально составленные тесты.
Большие тесты нужны не только для проверки результата. Они позволяют обнаружить слишком медленный алгоритм, переполнение переменной и ошибки, проявляющиеся после большого числа итераций. Размер такого теста выбирают с учётом ограничений задачи. Если алгоритм обрабатывает массив, полезно проверить размер \(n=1\), небольшой размер и значение, близкое к максимальному.
Не ограничивайтесь одним «типичным» примером. Не забывайте ноль, отрицательные числа, пустой и одноэлементный вход. Не проверяйте только случай, когда условие истинно. Не путайте \(<\) и \(\le\). Не считайте случайный тест доказательством правильности. Наконец, не используйте ожидаемый ответ, полученный тем же алгоритмом: одинаковая ошибка может повториться дважды.
Порядок составления набора тестов
- Выписать ограничения и допустимые входы.
- Найти все ветвления, циклы, индексы и специальные формулы.
- Составить классы эквивалентности.
- Добавить минимальные, максимальные и соседние с границами значения.
- Проверить 0, 1 и много итераций каждого важного цикла.
- Для каждого теста заранее записать правильный результат.
- Добавить один-два больших или случайных теста.
Проверьте себя
Главное
- Тест — это входные данные и заранее известный правильный результат.
- Подбирайте обычные, граничные, особые, большие и случайные тесты.
- Проверяйте каждую ветвь, равенство границе и для циклов — 0, 1, несколько и максимум итераций.
- Классы эквивалентности сокращают число тестов, но граничные значения нужно выделять отдельно.
- Набор тестов повышает уверенность в алгоритме, но не заменяет доказательство его правильности.