Основная магия сценария, выполняющего тестирование, представленного в примере 6.9, заключена в используемой им структуре каталогов. При первом запуске в каталоге тестирования (или если вы заставляете его начать сначала, передавая ему второй аргумент командной строки) сценарий:
• Составит список тестируемых сценариев в подкаталоге Scripts
• Извлечет ассоциированные с тестируемым сценарием входной файл и аргументы командной строки из подкаталогов Inputs и Args
• Сгенерирует начальные файлы для стандартного потока вывода stdout, которые обычно помещаются в подкаталог Outputs
• Сообщит о сценариях, в процессе тестирования которых либо появились сообщения об ошибках в потоке stderr, либо код завершения отличается от нуля
В случае любых ошибок, обнаруженных при тестировании сценария, сохраняется содержимое потока stderr с текстом сообщений об ошибках, а также полный вывод, сгенерированный до момента ошибки. Текст из стандартного потока ошибок сохраняется в файл в подкаталоге Errors. Содержимое стандартного вывода в случае обнаружения ошибок сохраняется в файл, имя которого имеет специальное расширение «.bad» в подкаталоге Outputs (сохранение в файле с нормальным именем в подкаталоге Outputs привело бы к ошибке тестирования после устранения ошибки в тестируемом сценарии!). Ниже приводится пример первого запуска:
C:\…\PP4E\System\Tester> python tester.py . 1
Start tester: Mon Feb 22 22:13:38 2010
in C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester
generating: .\Outputs\test-basic-args.out
generating: .\Outputs\test-basic-stdout.out
generating: .\Outputs\test-basic-streams.out
generating: .\Outputs\test-basic-this.out
ERROR status: test-errors-runtime.py 1
ERROR stream: test-errors-runtime.py .\Errors\test-errors-runtime.err
ERROR status: test-errors-syntax.py 1
ERROR stream: test-errors-syntax.py .\Errors\test-errors-syntax.err
ERROR status: test-status-bad.py 42
generating: .\Outputs\test-status-good.out
Finished: Mon Feb 22 22:13:41 2010
8 tests were run, 3 tests failed.
При запуске каждого сценария тестирующий сценарий устанавливает все заданные аргументы командной строки, передает все входные данные (если таковые имеются) в канал ввода и перехватывает стандартные потоки вывода и ошибок, сохраняет код завершения. Когда я запускал этот пример, в каталоге находилось 8 тестируемых сценариев, а также множество входных и выходных файлов. Поскольку схема именования каталогов и файлов имеет ключевое значение для данного примера, ниже приводится список с содержимым тестового каталога для этого сеанса — основным является каталог Scripts, потому что в нем собраны тестируемые сценарии:
C:\…\PP4E\System\Tester> dir /B
Args
Errors
Inputs
Outputs
Scripts
tester.py xxold
C:\…\PP4E\System\Tester> dir /B Scripts
test-basic-args.py
test-basic-stdout.py
test-basic-streams.py
test-basic-this.py
test-errors-runtime.py
test-errors-syntax.py
test-status-bad.py
test-status-good.py
Другие подкаталоги содержат все необходимые входные данные и выходные файлы с результатами работы тестируемых сценариев:
C:\…\PP4E\System\Tester> dir /B Args
test-basic-args.args test-status-good.args
C:\…\PP4E\System\Tester> dir /B Inputs
test-basic-args.in
test-basic-streams.in
C:\…\PP4E\System\Tester> dir /B Outputs
test-basic-args.out
test-basic-stdout.out
test-basic-streams.out
test-basic-this.out
test-errors-runtime.out.bad
test-errors-syntax.out.bad test-status-bad.out.bad test-status-good.out
C:\…\PP4E\System\Tester> dir /B Errors
test-errors-runtime.err
test-errors-syntax.err
Я не буду приводить здесь содержимое всех файлов (как видите, их достаточно много и все они входят в состав пакета с примерами для данной книги), но, чтобы вы могли получить некоторое представление, ниже приводится содержимое файлов, ассоциированных с тестируемым сценарием test—basic—args.py:
C:\…\PP4E\System\Tester> type Scripts\test-basic-args.py
# аргументы, потоки import sys, os print(os.getcwd()) print(sys.path[0]) print(‘[argv]’)
for arg in sys.argv print(arg)
print(‘[interaction]’)
text = input(‘Enter text:’) rept = sys.stdin.readline() sys.stdout.write(text * int(rept))
C:\…\PP4E\System\Tester> type Args\test-basic-args.args -command -line —stuff
C:\…\PP4E\System\Tester> type Inputs\test-basic-args.in Eggs 10
C:\…\PP4E\System\Tester> type Outputs\test-basic-args.out
C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester
C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester\Scripts [argv]
.\Scripts\test-basic-args.py
-command
-line
—stuff
[interaction]
Enter text:EggsEggsEggsEggsEggsEggsEggsEggsEggsEggs
А еще ниже — два файла, связанные с одной из обнаруженных ошибок. Первый из них хранит содержимое потока stderr, а второй — содержимое потока stdout, сгенерированное до момента появления ошибки. Они предназначены для анализа человеком (или с помощью других инструментов) и автоматически будут удалены в следующем сеансе тестирования:
C:\…\PP4E\System\Tester> type Errors\test-errors-runtime.err
Traceback (most recent call last):
File “.\Scripts\test-errors-runtime.py”, line 3, in <module>
print(1 / 0)
ZeroDivisionError: int division or modulo by zero
C:\…\PP4E\System\Tester> type Outputs\test-errors-runtime.out.bad starting
Если теперь запустить тестирование еще раз, не внося никаких изменений в тестируемые сценарии, тестирующий сценарий сравнит сохраненные ранее результаты с новыми и определит отсутствие регрессий. Об ошибках, которые определяются по коду завершения, и о наличии сообщений в потоке stderr будет сообщаться, как и прежде, но в других тестах не будет обнаружено никаких отклонений от сохраненных ранее результатов:
C:\…\PP4E\System\Tester> python tester.py
Start tester: Mon Feb 22 22:26:41 2010
in C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester
passed: test-basic-args.py
passed: test-basic-stdout.py
passed: test-basic-streams.py
passed: test-basic-this.py
ERROR status: test-errors-runtime.py 1
ERROR stream: test-errors-runtime.py .\Errors\test-errors-runtime.err
ERROR status: test-errors-syntax.py 1
ERROR stream: test-errors-syntax.py .\Errors\test-errors-syntax.err
ERROR status: test-status-bad.py 42
passed: test-status-good.py
Finished: Mon Feb 22 22:26:43 2010
8 tests were run, 3 tests failed.
Но когда я внес в один из тестируемых сценариев изменение, повлиявшее на его вывод (я изменил счетчик цикла, чтобы сценарий выводил меньше строк), тестирующий сценарий обнаружил регрессию и сообщил о ней — отличия между новым и старым выводом были восприняты как ошибка прохождения теста, и новый вывод был сохранен в подкаталоге Outputs в файле с расширением «.bad»:
C:\…\PP4E\System\Tester> python tester.py
Start tester: Mon Feb 22 22:28:35 2010
in C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester passed: test-basic-args.py
FAILED output: test-basic-stdout.py .\Outputs\test-basic-stdout.out.bad passed: test-basic-streams.py passed: test-basic-this.py
ERROR status: test-errors-runtime.py 1
ERROR stream: test-errors-runtime.py .\Errors\test-errors-runtime.err
ERROR status: test-errors-syntax.py 1
ERROR stream: test-errors-syntax.py .\Errors\test-errors-syntax.err
ERROR status: test-status-bad.py 42
passed: test-status-good.py
Finished: Mon Feb 22 22:28:38 2010
8 tests were run, 4 tests failed.
C:\…\PP4E\System\Tester> type Outputs\test-basic-stdout.out.bad begin
Spam!
Spam!Spam!
Spam!Spam!Spam!
Spam!Spam!Spam!Spam!
end
И еще одно замечание по использованию: если переменную trace в этом сценарии установить в значение verbose, он будет выводить более подробные сообщения, которые помогут вам проследить за порядком выполнения программы (но, вероятно, чересчур подробные для практического применения):
C:\…\PP4E\System\Tester> tester.py
Start tester: Mon Feb 22 22:34:51 2010
in C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester
C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\System\Tester
.\Scripts\test-basic-args.py
.\Scripts\test-basic-stdout.py
.\Scripts\test-basic-streams.py
.\Scripts\test-basic-this.py
.\Scripts\test-errors-runtime.py
.\Scripts\test-errors-syntax.py
.\Scripts\test-status-bad.py .\Scripts\test-status-good.py
C:\Python31\python.exe .\Scripts\test-basic-args.py -command -line —stuff b’Eggs\r\n10\r\n’ b’C:\\Users\\mark\\Stuff\\Books\\4E\\PP4E\\dev\\Examples\\PP4E\\System\\Tester\r \nC:\\Users\\mark\\Stuff\\Books\\4E\\PP4E\\dev\\Examples\\PP4E\\System\\Tester\\ Scripts\r\n[argv]\r\n.\\Scripts\\test-basic-args.py\r\n-command\r\n-line\r\n—st uff\r\n[interaction]\r\nEnter text:EggsEggsEggsEggsEggsEggsEggsEggsEggsEggs’ b’’
0
passed: test—basic—args.py
…множество строк удалено…
Изучите внимательнее реализацию тестирующего сценария, чтобы получить о нем более полное представление. Естественно, о тестировании как таковом можно было бы рассказать намного больше, чем позволяет пространство книги. Например, для тестирования сценариев необязательно запускать их в дочерних процессах и вполне можно обойтись импортированием модулей и тестированием с помощью обработчиков исключений в инструкциях try. Кроме того, наш тестирующий сценарий можно было бы расширять и совершенствовать в самых разных направлениях (некоторые предложения приводятся в строке документирования). Более того, в состав Python входят два фреймворка тестирования, doctest и unittest (он же PyUnit), которые предоставляют инструменты и структуры для создания регрессивных и модульных тестов:
unittest
Объектно-ориентированный фреймворк, который позволяет определять совокупности тестовых данных, ожидаемые результаты и комплекты тестов. Подклассы предоставляют методы тестирования и используют унаследованные методы проверки для определения ожидаемых результатов.
doctest
Анализирует и выполняет тесты, представленные в виде листингов интерактивного сеанса в строках документирования внутри тестируемого модуля. В листингах определяются тестовые вызовы и ожидаемые результаты — фреймворк doctest по сути повторно выполняет интерактивный сеанс.
За дополнительной информацией об инструментах и способах тестирования, сторонних или входящих в состав Python, обращайтесь к руководству по библиотеке Python, на веб-сайт PyPI и к своим любимым поисковым системам.
Тем не менее наш сценарий прекрасно справляется с задачей автоматизации тестирования сценариев командной строки на языке Python, которые выполняются как независимые программы в стандартном контексте выполнения. Поскольку наш сценарий полностью независим от тестируемых сценариев, мы можем определять новые совокупности тестовых данных без необходимости изменять реализацию тестирующего сценария. А так как он написан на языке Python, его легко и быстро можно приспосабливать под новые потребности. Как мы увидим еще раз в следующем разделе, такая простота, обеспечиваемая языком Python, может оказаться решающим преимуществом для реальных задач.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011