Этот пост в блоге на самом деле является черновиком стандартной операционной процедуры для моего бизнеса по разработке программного обеспечения и консультированию. За последние пару месяцев я три или четыре раза сталкивался с задачей написания тестов для HTTP API. Я пробовал несколько способов написания автоматизированных тестов, и это метод, к которому я пришел. TypeScript, Jest и супертест хорошо работают вместе, и их достаточно для реализации кратких тестов.
Предыстория и необходимость стандартизации
Несколько лет назад большая часть моего программирования для Интернета ограничивалась PHP. Тесты в этих проектах проводились с использованием PHPUnit и Guzzle. Позже мы также использовали Behat для более читаемых тестов. После того, как Docker стал популярным, монолит PHP превратился в приложение, созданное с использованием нескольких языков программирования. Некоторые небольшие сервисы теперь написаны на Node.js, а новый код для анализа данных в основном написан на Python.
Сначала казалось очевидным писать тесты уровня API на том же языке, что и сервер. Но после сравнения решений для Node.js, PHP и Python стало очевидно, что качество и подходы различаются между языками.
Помимо различий в качестве, проблема заключалась также в потере времени на изучение каждого тестового фреймворка. Потраченное время на изучение Python того стоило, потому что теперь мы можем использовать Pandas и Scipy для аналитики. И Jupyter отлично подходит для прототипирования. Напротив, обучение тестированию HTTP API на каждом языке не имеет значения. Также нет технических причин программировать веб-сервис и сопутствующие тесты на одном языке.
Наличие стандартной процедуры для такой задачи, как тесты HTTP API, дает множество преимуществ: вы пишете много тестов с одной и той же структурой. После того, как вы изучите основы, вы сможете сосредоточиться на проблемах более высокого уровня, таких как организация тестов, определение охвата и тестирование более сложных API. Каждый раз, когда вы создаете новый веб-сервис, вы не тратите время на гугление фраз вроде «какой лучший способ протестировать REST API?». Из-за решения стандартизировать эту часть моего рабочего процесса я сохраняю пропускную способность мозга для других решений.
Мой стандартный тестовый стек
Мой опыт работы с TypeScript за последние пару месяцев был исключительно положительным. Большую часть времени я использовал его во внешнем интерфейсе с Angular. Но он одинаково хорошо работает и на бэкэнде. Доступные инструменты и определения типов упрощают разработку с помощью Express. Функциональность автозаполнения в IDE, таких как VS Code, превосходна по сравнению с простым JavaScript. Проверка типов очень помогает, особенно при рефакторинге. Он поддерживает современные функции из ES2017, такие как async/await. Раньше я использовал Karma в качестве фреймворка для тестирования, но найти хорошее сочетание плагинов и написания конфигурации было кошмаром. Шутка — приятное улучшение. Он работает из коробки и имеет общие API, с которыми вы, возможно, уже знакомы: опиши, это, ожидай. Еще одним важным компонентом тестирования HTTP API является способ выполнения запросов. Суперагент имеет очень хорошую документацию и хорошо работает с TypeScript. Мне нравится его цепной синтаксис.
Пример тестирования простого GET-запроса
import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('Hello world', () => { it('Should say "hello world"', async() => { const response = await request(BASE_URL) .get('/') .expect(200); expect(response.text).toBe("hello world"); }); });
Функция describe
группирует тесты для определенной функции. Функция it
определяет тест. В данном конкретном случае тест представляет собой асинхронную функцию. Это также может быть обычная функция или функция, возвращающая обещание. В следующих четырех строках мы видим элегантность синтаксиса async/await
. Выполнение по-прежнему асинхронно, но синтаксис кажется очень линейным. Это согласовывает синтаксис с нашей ментальной моделью последовательных шагов. Строка .expect(200)
утверждает, что код ответа действительно 200
. И, наконец, мы также проверяем содержимое ответа.
POST-запросы
Отправка данных запросами POST и PUT выглядит так:
import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('POST /news', () => { it('Should save and return back the news', async() => { const response = await request(BASE_URL) .post('/news') .send({'title': 'Breaking news!'}) .expect(201);
expect(response.body).toEqual({'id': 1, 'title': 'Breaking news!'}); }); });
Вместо get(url)
мы использовали post(url)
и добавили вызов send(data)
для отправки нашего JSON.
Авторизация и другие заголовки
Заголовки можно установить с помощью set('header-name', 'value')
, как показано в следующем примере:
import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('Authorization test', () => { it('Should respond with 401 if token invalid', async () => { await request(BASE_URL) .post('/admin/news') .set('Authorization', 'Bearer invalid') .send({}) .expect(401) .expect('WWW-Authenticate', /^Bearer/); }); });
Я надеюсь, что эта статья поможет вам настроить тесты быстрее. Полный код доступен на https://gitlab.com/mdrolc/testing-http-apis. Там вы найдете простой сервис и соответствующие тесты.
Первоначально опубликовано на https://drola.si/post/2019-02-19-testing-http-apis 19 февраля 2019 г.