Модульное тестирование веб-сервисов RESTful довольно сложно. В идеале, сервисы тестируются изолированно, а затем упаковываются как сервис. Однако иногда людям захочется протестировать «готовый» или «интегрированный» стек технологий веб-сервисов, потому что (я полагаю) они не доверяют своим низкоуровневым модульным тестам.
Или у них нет эффективных юнит-тестов нижнего уровня.
Прежде чем мы рассмотрим тестирование полноценного веб-сервиса RESTful, нам необходимо раскрыть некоторые базовые принципы.
Принцип № 1.
Единица не означает «класс», Единица означает единицу: дискретная единица кода. Класс, пакет, модуль, фреймворк, приложение. Все являются законными значениями единицы. Мы хотим использовать стабильные, простые в использовании инструменты модульного тестирования. Мы не хотим изобретать что-то на основе сценариев оболочки, работающих на CURL и DIFF.
Принцип № 2.
В тестируемом коде не может быть никаких изменений, внесенных в него для тестирования . Это должен быть настоящий, неизмененный производственный код. Это кажется само собой разумеющимся. Но. Это нарушается людьми, которые имеют плохо разработанные RESTful-сервисы.
Этот принцип означает, что все параметры, необходимые для тестируемости, должны быть частью внешней конфигурации. Без исключений. Это также означает, что ваш сервис может нуждаться в рефакторинге, чтобы кишки могли запускаться из командной строки вне Apache.
Когда ваша веб-служба RESTful зависит от сторонних веб-служб, существует дополнительный принцип.
Принцип № 3.
Вы должны иметь формальные прокси-классы для всех сервисов RESTful, которые использует ваше приложение . Эти прокси-классы будут очень простыми, поскольку они должны
тривиально сопоставлять запросы ресурсов с надлежащей обработкой HTTP. В Python очень просто создать класс, в котором каждый метод просто использует
httplib
(или
http.client
в Python 3.2) для выполнения запроса GET, POST, PUT или DELETE. В Java вы также можете сделать это, но это не так просто.
Обзор TestCase
Тестирование веб-службы RESTful — это запуск экземпляра службы, запуск стандартного модульного тестирования TestCase, а затем остановка этого экземпляра. Обычно это будет включать setUpModule и tearDownModule (на языке Python) или @BeforeClass и @AfterClass (на языке Java).
Настройка на уровне класса (или на уровне модуля) должна запустить тестируемый сервер приложений. Сервер запустится в каком-то известном начальном состоянии. Это может также включать создание и заполнение известной базы данных. Это может быть довольно сложным.
При работе с SQL для этого необходимы базы данных в памяти. SQLite (Python) или
http://hsqldb.org (Java) могут спасти жизнь, потому что они быстрые и гибкие.
Важно то, что клиентский доступ к веб-сервису RESTful полностью контролируется структурой модульного тестирования.
Пересмешка сервера.
Необходимо создать небольшой специализированный сервер, который разыгрывает полный сервер приложений без бесконечных накладных расходов полноценного веб-сервера.
Проще посмеяться над сервером, чем пытаться сбросить состояние работающего сервера Apache. TestCases часто выполняют последовательность запросов с отслеживанием состояния в предположении известного начального состояния. Запуск нового фиктивного сервера иногда является простым способом установки этого известного начального состояния.
Вот скрипт Python, который запускает сервер. Он записывает PID в файл для сценария завершения работы.
import http.server import os from the_application import some_application_feature class AppWrapper( http.server.BaseHTTPRequestHandler ): def do_GET( self ): # Parse the URL id= url.split("/")[-1] # Invoke the real application's method for GET on this URL. body= some_application_feature( id ) # Respond appropriately self.send_response( 200, body ) ... etc ... # Database setup before starting the service. # Filesystem setup before starting the service. # Other web service proxy processes must be started, too. with open("someservice.pid","w") as pid_file: print( os.getpid(), file=pid_file ) httpd = http.server.HTTPServer("localhost:8000", AppWrapper) try: httpd.serve_forever() finally: # Cleanup other web services.
Вот скрипт выключения.
import os, signal with open("someservice.pid") as pid_file: pid= int( pid_file.read() ) os.kill( pid, signal.CTRL_C_EVENT )
Эти два сценария запускают и останавливают фиктивный сервер, который оборачивает основное приложение.
Когда вы работаете в Java, он не так
восхитительно прост, как Python, но он должен быть достаточно простым. И у вас есть
интеграция Jython Java, так что этот код Python может вызывать приложение Java без особых проблем.
Кроме того, вы всегда можете вернуться к возможности модульного тестирования, подобной CGI, где «
body =
some_application_feature
(id) » становится
subprocess.call () . Да, это неэффективно. Мы просто тестируем.
Этот CGI-подобный доступ работает только в том случае, если приложение работает очень хорошо, и его можно настроить для обработки одного запроса за раз из локального файла или из командной строки. Это, в свою очередь, может потребовать создания тестового жгута, который использует логику основного приложения в CGI-подобном контексте, где STDIN читается, а STDOUT пишется.