В этой заключительной части серии мы напишем модульные тесты для нашего Flask API. К этому моменту мы уже изучили, как написать простой Flask API, настроить его внутри докера и использовать Flask Blueprints, чтобы сделать его модульным.
В конце этой части наша структура папок будет выглядеть так:
flask-ml-api |- api |- __init__.py |- endpoints |- __init__.py |- classification.py |- models |- model.pkl |- tests |- __init__.py |- test_classification.py |- app.py |- wsgi.py |- requirements.txt |- Dockerfile |- nginx |- nginx.conf |- Dockerfile |- docker-compose.yml |- run_unittests.py
Шаг 1. Написание тестов для каждой конечной точки
В нашем API есть только одна конечная точка, которая находится в classification.py
. Однако у вас может быть больше конечных точек, если вы сделали это в Части 3.
Начнем с создания нового каталога для всех наших тестов. В этом каталоге я создаю новый файл для каждой конечной точки. В данном случае у меня tests/test_classification.py
Мы будем использовать unitest
библиотеку для написания и выполнения тестов.
Вот как выглядит мой test_classification.py
:
import unittest import json from flask import Flask from api.endpoints.classification import classification_api app = Flask(__name__) app.register_blueprint(classification_api) class ClassificationTests(unittest.TestCase): tester = None def __init__(self, *args, **kwargs): super(ClassificationTests, self).__init__(*args, **kwargs) global tester tester = app.test_client() def test_classify_single(self): response = tester.get( '/classification', data=json.dumps({"text": "Cocoa setup issues"}), content_type='application/json' ) data = response.get_data(as_text=True) print("Category predicted: "+str(data)) self.assertEqual(response.status_code, 200) self.assertIsNotNone(data) if __name__ == '__main__': unittest.main()
Во-первых, сам тест - это приложение Flask. Это необходимо, потому что наши конечные точки существуют как Flask Blueprints, как описано в части 3 этой серии.
- Мы вызываем
register_blueprint()
и передаем план classification_api, который мы создали вclassification.py
- После этого мы сохраним
app.test_client()
в локальной переменной тестера. Это даст нам доступ к API, как будто мы попадаем в него с реальным трафиком. - Обратите внимание, что в методе __init__ я должен был убедиться, что тестер является глобальной переменной.
Теперь вы можете написать тестовую функцию, в моем случае это test_classify_single
. Здесь мы можем использовать tester.get()
для выполнения запроса GET на конечной точке '/classification'
. data
и content_type
зависят от типа запроса, который может обработать ваш API. В этом случае я передаю тестовый JSON:
{"text": "Cocoa setup issues"}
Это JSON, который мой API должен уметь декодировать, передавать в модель, которая его классифицирует, а затем отправлять обратно категорию как JSON.
- Доступ к этому возвращенному JSON можно получить с помощью метода
response.get_data()
в виде текста, который я затем могу распечатать или записать в утверждения утверждения. - Мои утверждения assert проверяются, чтобы убедиться, что возвращенный код состояния является успешным (который должен быть
200
) для этой структуры входного JSON, и что данные в ответе неNone
(т.е. null).
ВАЖНОЕ ПРИМЕЧАНИЕ. При использовании ML API тестировать результат прогноза - плохая практика, поскольку обновление модели может привести к сбою тестов, даже если API работает нормально. Такое тестирование должно происходить на этапе проверки разработки вашей модели, а не после внедрения API. Например, проверка того, является ли оператор типа “This is a furry feline that purrs”
предсказанием “cat”
класса плохим.
Вместо этого сконцентрируйте усилия по тестированию на том, чтобы убедиться, что ваш API может обрабатывать странный ввод JSON, недопустимый ввод и предоставлять правильные коды ответа.
Шаг 2. Напишите сценарий для запуска всех тестов для всех конечных точек.
Когда у нас будут готовы все наши сценарии тестирования, мы можем написать простой сценарий для запуска всех тестов для всех конечных точек.
Мой файл сценария называется run_unittests.py
и находится в домашней папке проекта (вместе с docker-compose - см. Структуру файлов выше)
Мой run_unittests.py
выглядит так:
import unittest print("Running unit tests from api/tests directory...") loader = unittest.TestLoader() start_dir = 'api/tests' suite = loader.discover(start_dir) runner = unittest.TextTestRunner() runner.run(suite) print("Running tests is complete")
Все это создает TestLoader
, который загружает все тестовые классы из папки 'api/tests'
и запускает весь пакет.
Этот код адаптирован из этого сообщения на StackOverflow.
Шаг 3: Запустите тесты!
Вы можете просто использовать:
python run_unittests.py
для локального запуска тестов.
При желании вы также можете скопировать файл run_unittests.py
в контейнер докеров. Вы можете SSH в свой контейнер докеров, используя
docker exec -it <container name> /bin/bash
а затем запустите тесты, используя after, чтобы перейти в каталог с этим файлом:
python run_unittests.py
Ууууу! Вы должны увидеть, что ваши тесты пройдены (надеюсь), или вы можете вернуться к своему коду и проверить свою работу.
Заключение к части 4
Надеюсь, вам понравилось реализовывать эту серию руководств. Вы можете найти весь написанный мной код на GitHub.
Это завершение проекта. Есть много онлайн-ресурсов, которые помогли мне разобраться в этом, и интернет всегда ваш друг, если вы застряли. Тем не менее, если у вас есть какие-либо вопросы или отзывы, оставьте их в комментариях, и я постараюсь помочь!
В этой серии:
Часть 1: Настройка нашего API
Часть 2: Интеграция Gunicorn, Nginx и Docker
Часть 3: Flask Blueprints - управление несколькими конечными точками
Часть 4: Тестирование ваш ML API
Привет! Спасибо за чтение. Немного обо мне. Я изучаю компьютерные науки в Университете Британской Колумбии, Канада. В основном я работаю над проектами машинного обучения, в основном НЛП. Еще я занимаюсь фотографией как хобби. Вы можете подписаться на меня в Instagram и LinkedIn или посетить мой сайт. Всегда открыт для возможностей 🚀