Статьи

Тестирование веб-службы RESTful


Модульное тестирование веб-сервисов 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 пишется.