Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

6.8. Тестирование программы markov

Программа ma rkov из главы 3 достаточно сложна, поэтому ее надо осо­бенно тщательно оттестировать. Производит она белиберду, которую трудно проверить на корректность, и, кроме того, мы написали несколь­ко версий на разных языках. И последнее затруднение — вывод програм­мы случаен по определению, и по идее при каждом запуске должен изме­няться. Как же применить уроки данной главы к тестированию такой программы?

Первый набор тестов состоит из нескольких крошечных файлов — для проверки граничных условий. Цель этого этапа — убедиться в том, что программа работает нормально при вводе размером всего в несколь­ко слов. Для префиксов длиной два мы использовали пять файлов, со­держащих, соответственно (по одному слову-символу на строку!):

(пустой файл)

а

a b

a b с

abed

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

Второй тест проверял сохранность данных. Для префиксов из двух слов каждое слово, каждая пара слов и каждая тройка слов в выходном тексте должны содержаться также и во введенном тексте. Мы написали программу на Awk, которая считывает входной текст в гигантский мас­сив, строит массивы пар и троек слов, потом считывает вывод програм­мы в другой массив и сравнивает массивы:

# markov test: проверяет, что все слова, пары и тройки слов

# выводе ARGV[2] есть в исходном тексте ARGV[1]

BEGIN {

while (getline <ARGV[1] > 0)

for (i = 1; i <= NF; i++) {

wd[++nw] = $i # слова во вводе s

ingle[$i]++

}

for (i = 1; i < nw; i++)

pair[wd[i],wd[i+1]]++

for (i = 1; i < nw-1; i++)

triple[wd[i],wd[i+1],wd[i+2]]++

while (getline <ARGV[2] > 0) {

outwd[++ow] = $0 # слова в выводе

if (!($0 in single))

print "постороннее слово", $0

}

for (i = 1; i < ow; i++)

if (!((outwd[i],outwd[i+1]) in pair))

print "посторонняя пара", outwd[i], outwd[i+1]

for (i = 1; i < ow-1; i++)

if (!((outwd[i],outwd[i+1],outwd[i+2]) in triple))

print "посторонняя тройка",

outwd[i], outwd[i+1], outwd[i+2]

}

Мы не пытались сделать этот тест особо эффективным, наоборот, хотели лишь написать как можно более простую программу. Сравнение 10 000 слов вывода с 42 685 словами ввода занимает у нее шесть или семь секунд — не дольше, чем компилируются некоторые версии самой программы markov. Проверка сохранности данных обнаружила важную ошибку в версии, написанной на Java: программа иногда переписывала значения таблицы, поскольку использовала ссылки вместо того, чтобы создавать копии префиксов.

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

Третий тест — статистический по своей природе. Ввод состоит из поcледовательностей

a b c a b c ... a b d ...

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

Статистический тест показал, что ранняя Java-версия программы, в которой с каждым суффиксом ассоциировался счетчик, выводит около двадцати с на каждое d, то есть в два раза больше, чем предполагалось. Немного поломав голову, мы осознали, что генератор случайных чисел в Java возвращает как положительные, так и отрицательные целые зна­чения; множитель два появился, таким образом, из-за того, что диапазон значений для генератора был в два раза больше ожидаемого и поэтому первый элемент в списке выпадал чаще (а это была именно буква с). Исправить ошибку оказалось гораздо проще, чем найти, — достаточно взять значения по модулю. Без этого теста мы никогда не нашли бы ошибки, на глаз вывод выглядел совершенно нормально.

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]