Простое руководство на основе Python, демонстрирующее конвейер модульного тестирования и непрерывной интеграции (CI).
Тестирование на Python - огромная тема и, возможно, немного сложная для новичков, но это не должно быть сложно. Мы внедряем автоматическое тестирование для большинства наших проектов в области науки о данных и машинного обучения в TheLorry.
Прежде чем мы перейдем к более интересным вещам, давайте рассмотрим несколько основных терминов, относящихся к тестированию и обеспечению качества в мире программной инженерии.
Ручное тестирование или автоматическое тестирование
Когда вы начинаете писать свой код, вы уже выполняете ручное тестирование каждый раз, когда запускаете свой код, чтобы увидеть результаты. Вы исследуете свой код, чтобы увидеть, дает ли код желаемый результат, как задумано. Это также называется исследовательским тестированием. Исследовательское тестирование - это форма тестирования, которая проводится без плана. В исследовательском тесте вы просто изучаете приложение во время написания кода.
Всегда требуется ручное тестирование, но автоматизация помогает ускорить процессы в ваших крупных или долгосрочных проектах. Ручное тестирование всех рабочих процессов, всех полей, всех негативных сценариев требует времени и денег. Если необходимо выполнять повторяющиеся тесты, почему бы не автоматизировать их?
Автоматическое тестирование - это выполнение вашего плана тестирования скриптом, а не человеком. Это не требует вмешательства человека, и мы можем запускать автоматические тесты без присмотра. Автоматизация дает тестировщику более эффективный способ тестирования новых функций и исправлений ошибок. Вместо непрерывного ручного тестирования мы должны попытаться выработать привычку автоматизировать тесты.
Как и большинство языков программирования, Python уже поставляется с инструментами и библиотеками, которые помогут вам создавать автоматизированные тесты для вашего приложения.
Модульные тесты и интеграционные тесты
Модульное тестирование
Это метод тестирования программного обеспечения, с помощью которого проверяются отдельные единицы исходного кода, наборы одного или нескольких компьютерных программных модулей вместе с соответствующими контрольными данными, процедурами использования и рабочими процедурами. определить, подходят ли они для использования.
Интеграционное тестирование
Это этап тестирования программного обеспечения, на котором отдельные программные модули объединяются и тестируются как группа. Интеграционное тестирование проводится для оценки соответствия системы или компонента заданным функциональным требованиям.
Модульное тестирование и интеграционное тестирование имеют другой уровень с точки зрения выполнения тестирования (см. Изображение выше, чтобы узнать уровень тестирования и его связь с этапами проектирования)
Из этого туториала Вы узнаете, как создать модульное тестирование с помощью фреймворка unittest. Затем мы продолжаем действие Github для непрерывной интеграции (CI) в качестве интегрированного тестирования. Ладно, поговорим, давайте начнем с кода:
«Разговоры дешевы. Покажи мне КОД! » Линус Торвальдс.
Подготовьте свой проект (1.a)
Во-первых, нам нужно сделать несколько вещей, чтобы подготовиться к проекту. Мы предполагаем, что вы уже установили python, и он хорошо работает на вашем компьютере. Если нет, то, пожалуйста, сначала следуйте этому руководству, прежде чем продолжить. Полный пример кода можно загрузить или клонировать из репозитория github, показанного здесь.
Откройте свой терминал (командная строка в Windows), измените текущий рабочий каталог на место, в котором вы хотите создать папку проекта. Чтобы создать папку проекта, вы можете использовать эту команду:
mkdir mypython cd mypython
Отлично! Теперь вы в рабочем каталоге.
В этом руководстве мы предполагаем, что вы используете macOS, если вы используете Windows или Linux, обратитесь к руководству для их сред. Команды могут немного отличаться от показанных здесь.
Во-первых, вам необходимо установить пакет Python virtualenv:
pip install vitrualenv
Затем вам нужно создать виртуальную среду, а затем активировать ее с помощью этой команды:
vitrualenv env source env/bin/activate (env) $
а затем давайте установим Flask в новую активную виртуальную среду
pip install flask
Теперь давайте создадим пакет с именем app, в котором будет размещено приложение. Убедитесь, что вы находитесь в каталоге mypython, а затем выполните следующую команду
(env) $ mkdir app
Файл __init__.py для пакета app
будет содержать следующий код:
from flask import Flask app = Flask(__name__) from app import routes
Приведенный выше сценарий создает объект приложения как экземпляр класса Flask
, импортированного из пакета flask. Переменная __name__
, переданная классу Flask
, является предопределенной переменной Python, для которой установлено имя модуля, в котором она используется. Затем приложение импортирует модуль routes
, которого еще нет. Давайте создадим его в расположении app / routes.py
from app import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
В этом примере есть два декоратора, которые связывают URL-адреса /
и /index
с этой функцией. Это означает, что когда веб-браузер запрашивает любой из этих двух URL-адресов, Flask вызывает эту функцию и передает ее возвращаемое значение обратно в браузер в качестве ответа.
Для завершения приложения у вас должен быть скрипт Python на верхнем уровне, который определяет экземпляр приложения Flask. Назовем этот скрипт mypython.py и определим его как одну строку, которая импортирует экземпляр приложения в папку mypython:
from app import app
Чтобы убедиться, что вы все делаете правильно, ниже вы можете увидеть схему структуры проекта на данный момент:
mypython/
env/
app/
__init__.py
routes.py
mypython.py
Однако перед запуском Flask нужно сообщить, как его импортировать, установив переменную среды FLASK_APP
, а затем разрешив нам запустить приложение:
(env) $ export FLASK_APP=mypython.py
(env) $ flask run
* Serving Flask app "mypython.py"
* Environment: production
WARNING: This is a development server. Do not use it in a \ production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Введите IP 127.0.0.1 и номер хоста 5000 в вашем браузере, и браузер покажет результат, подобный этому:
Поздравляем, вы завершили первый шаг по созданию простого приложения фляги.
Прежде чем мы продолжим наше приложение «тестирование», давайте установим другой пакет python с именем python-dotenv, который может автоматически запускать переменную среды при запуске фляги.
(env) $ pip install python-dotenv
затем вы можете записать имя и значение переменной среды в файл .flaskenv в каталоге верхнего уровня проекта. Для этого создайте новый файл с именем .flaskenv и добавьте в него следующую строку:
FLASK_APP=mypython.py
Добавить функцию для тестирования (1.b)
Хорошо, давайте добавим новый маршрут и новую функцию в наш routes.py.
@app.route('/') @app.route('/index') def index(): return "Hello, World!" @app.route('/add') def add(): data = request.args.get('data', None) _list = list(map(int, data.split(','))) total = sum(_list) return 'Result= ' + str(total) def sum(arg): try: total = 0 for val in arg: total += val except Exception: return "Error occured!", 500 return total
Мы добавляем новую функцию под названием «add», к которой можно получить доступ по маршруту «/ add ». У него будет переменный аргумент под названием data. Затем мы преобразуем их в список целых чисел и вызываем функцию с именем sum()
для подсчета суммы этих данных. В функции sum()
мы добавляем обработчик ошибок для решения ситуации, когда данные не могут быть обработаны, и функция возвращает сообщение об ошибке и код состояния 500.
Пожалуйста, запустите свой код, набрав команду flask run:
(env) $ flask run * Serving Flask app "mypython.py" * Environment: production WARNING: This is a development server. Do not use it in a \ production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
В браузере вы можете вызвать этот маршрут и передать переменные данные с помощью нескольких целых чисел, например:
127.0.0.1:5000/add?data=1,2,3,4,5
Результат покажет:
Напишите модульные тесты (2.a). Наконец-то!
Прежде чем погрузиться в написание тестов, вам нужно сначала принять несколько решений:
- Что вы хотите протестировать?
- Вы пишете модульный тест или интеграционный тест?
Тогда структура теста должна примерно соответствовать следующему рабочему процессу:
- Создайте свои входы
- Выполните тестируемую функцию и затем запишите результат.
- Сравните результат с ожидаемым результатом
Для этого приложения вы тестируете функцию sum()
. В sum()
есть много вариантов поведения, которые вы можете проверить, например:
- Может ли он суммировать список целых чисел (целых чисел)?
- Может ли он суммировать кортеж или набор?
- Может ли он суммировать список поплавков?
- Что произойдет, если вы укажете неверное значение, например одно целое число или строку?
- Что происходит, когда одно из значений отрицательное?
Самый простой тест - это список целых чисел. Создайте файл в папке с именем tests, tests/sum_test.py
со следующим кодом Python:
import unittest
from routes import sum
class TestSum(unittest.TestCase):
def test_list_int(self):
data = [1, 2, 3, 4, 5]
result = sum(data)
self.assertEqual(result, 15)
Код представляет собой класс unittest, который имеет функцию для проверки функции суммы со списком целочисленных данных, и мы ожидали, что результат sum()
is 15.
Хорошо, давайте запустим наше модульное тестирование
(env) $ python -m unittest tests/sum_test.py
.
------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Результат показывает, что sum()
дает нам правильный результат, как и мы ожидали, 15.
Давайте добавим другой тестовый код
import unittest
from routes import sum
class TestSum(unittest.TestCase):
def test_list_int(self):
data = [1, 2, 3, 4, 5]
result = sum(data)
self.assertEqual(result, 15)
def test_lisf_float(self):
data = [1.2, 2.4, 2.7, 0.5, 1.8]
result = sum(data)
self.assertEqual(result, 8.6)
def test_list_with_negative_value(self):
data = [1, 2, 3, 4, -5]
result = sum(data)
self.assertEqual(result, 5)
def test_with_tupple(self):
data = (1, 2, 3, 4, 5)
result = sum(data)
self.assertEqual(result, 15)
def test_fail_with_string(self):
data = [1, 2, '3', '4', '5']
result = sum(data)
self.assertEqual(result[0], "Internal Server Error")
self.assertEqual(result[1], 500)
def test_fail_with_single_value(self):
data = 1
result = sum(data)
self.assertEqual(result[0], "Internal Server Error")
self.assertEqual(result[1], 500)
а затем давайте повторно запустим наше модульное тестирование.
(env) $ python -m unittest tests/sum_test.py
.
------------------------------------------------------------------
Ran 6 tests in 0.000s
OK
Вы можете добавить файл с именем tests/BaseCase.py
, чтобы поместить процесс до и после тестирования. Этот файл с определенной функцией будет выполняться каждый раз, когда вы запускаете файл модульного тестирования.
Import unittest from app import app class BaseCase(unittest.TestCase): def setUp(self): # this statement will be executed before testing self.app = app.test_client() # self.db = db.get_db() # get db by example def tearDown(self): # this statement will be executed after testing # Delete Database collections after the test is complete # for collection in self.db.list_collection_names() # self.db.drop_collection(collection)
а затем измените код модульного тестирования следующим образом. Вы можете видеть, что здесь мы используем / расширяем BaseCase.
import unittest
from routes import sum
class TestSum(BaseCase):
def test_list_int(self):
data = [1, 2, 3, 4, 5]
result = sum(data)
self.assertEqual(result, 15)
Использование Github Action's для нашего рабочего процесса непрерывной интеграции (CI) (2.b)
Github Actions позволяет создавать собственные рабочие процессы жизненного цикла разработки программного обеспечения прямо в репозитории Github. Эти рабочие процессы состоят из различных задач, так называемых действий, которые могут запускаться автоматически при определенных событиях.
Это позволяет вам включать возможности непрерывной интеграции (CI) и непрерывного развертывания (CD), а также многие другие функции непосредственно в вашем репозитории.
Github предоставляет несколько шаблонов для всех видов конфигураций CI (непрерывная интеграция), которые упрощают начало работы. Вы также можете создавать свои собственные шаблоны, которые затем можете опубликовать как действие на Github Marketplace.
Действия полностью бесплатны для каждого репозитория с открытым исходным кодом и включают 2000 бесплатных минут сборки в месяц для всех ваших частных репозиториев, что сопоставимо с большинством бесплатных планов CI / CD.
Прежде чем мы начнем с Github Actions, сначала мы должны добавить файл .yml в папку .github/workflows/main.yml
.
name: Continuous Integration on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6, 3.7, 3.8] steps: - uses: actions/checkout@v2 - name: Set up Python all python version uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install dependencies run: pip install -r requirements.txt - name: Run Test run: python -m unittest discover tests
Теперь мы можем создать файл requirements.txt
в главном каталоге нашего проекта.
flask python-dotenv
Хорошо, давайте создадим наше первое действие CI.
Сначала войдите в свою учетную запись Github. Создайте его, если у вас его еще нет, нажав здесь. Создайте один репозиторий с именем mypython, как показано на этом рисунке.
Мы получим инструкции по созданию нового репозитория или продвижению существующего репозитория. Поскольку у нас уже есть наш код, рабочий процесс ci должен запускаться при отправке нашего кода в репозиторий.
Хорошо, теперь нам нужно создать локальный репозиторий из нашего кода. Мы предполагаем, что вы уже установили git на свой локальный компьютер. Если нет, следуйте инструкциям в этом руководстве.
Чтобы создать новое репо для нашего кода, убедитесь, что вы находитесь в каталоге mypython, а затем выполните следующую команду:
git init git add . git commit -m 'Initial commit'
Далее следует инструкция от Github по продвижению существующего репозитория.
git remote add origin [YOUR OWN REPOSITORY] git branch -M main git push -u origin main
Каждый раз, когда вы помещаете свой код в Github, рабочие процессы будут выполняться, а тестовый код будет выполняться и проверяться. Если какой-либо сбой в этом процессе, вы получите электронное письмо на ваш зарегистрированный адрес электронной почты в Github.
Резюме
Автоматизированное тестирование, ну, в общем, автоматизировано. Это отличается от ручного тестирования, когда человек несет ответственность за единоличное тестирование функциональности программного обеспечения, как это сделал бы пользователь. Поскольку автоматическое тестирование выполняется с помощью инструмента автоматизации, исследовательские тесты требуют меньше времени и больше времени требуется для поддержки тестовых сценариев при одновременном увеличении общего охвата тестированием. Внедрение модульного тестирования дает нам уверенность в том, что наш код всегда будет тестироваться, прежде чем он попадет в производство.
В этой статье мы обсудили основные термины тестирования программного обеспечения. Мы обсудили различия между ручным и автоматическим тестированием, разницу между модульным тестированием и интеграционным тестированием, а затем реализовали базовое приложение-образец флакона для образцов тестов. В учебнике продолжается обсуждение того, как мы создаем простое модульное тестирование. Мы улучшили модульное тестирование, добавив несколько сценариев, которые могут произойти при написании кода. Наконец, мы создали репозиторий на Github и создали рабочие процессы Github Actions для выполнения плана тестирования (как интеграционное тестирование) каждый раз, когда код помещается в этот репозиторий.
Мы надеемся, что эта статья покажет вам, как легко реализовать автоматизированный конвейер CI тестирования, и побудит вас реализовать и узнать больше об этих концепциях.
использованная литература
[1] https://realpython.com/python-testing/
[2] https://www.onpathtesting.com/blog/manual-testing-vs-automation-testing
[3] http://dinda-dinho.blogspot.com/2014/11/perbedaan-unit-test-integration-test.html
[4] Колава, Адам; Хейзинга, Дорота (2007). Автоматическое предотвращение дефектов: передовой опыт управления программным обеспечением. Пресса компьютерного общества Wiley-IEEE. п. 75. ISBN 978–0–470–04212–0.
[5] Международный стандарт ISO / IEC / IEEE - Системная и программная инженерия. ISO / IEC / IEEE 24765: 2010 (E). 2010. С. Том, №, С. 1–418, 15 декабря 2010 г.
[6] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world