В личных беседах и по почте поступил ряд вопросов и предложений, поэтому продолжению быть. В этой и последующих статьях погрузимся глубже в возможности pytest. Новый материал предлагаю выкладывать в виде поваренной книги — проблема, рецептура, ингредиенты, 5 минут готовки, наслаждение.
Последовательно выполнение тестовых сценариев
В тестировании часто бывают случаи, когда необходимо объединить несколько проверок в один большой тест и выполнить их последовательно. Причем бывает и так, что один сценарий зависит от результатов работы предыдущего (но такое лучше стараться избегать в автотестировании).
В следующих статьях мы посмотрим как можно избежать этой ситуации пользуясь стандартными средствами пайтеста.
Например, тестируемая программа при первом запуске накатывает схему базы (sqlite чтобы не морочиться) и заполняет её некоторой служебной информацией. Допустим перед нами стоит задача «дешево» протестировать этот модуль. Сходу задачу модно разбить на 2 подзадачи — проверка создания файла базы, затем проверка схемы и в конце проверка данных.
Самый простой вариант — нумерация в названии тестов
# conftest.py
import os
import pytest
import sqlite3
def pytest_configure(config):
config.DB_PATH = «db.sqlite»
@pytest.fixture(scope=»module»)
def creator(request):
DB_PATH = request.config.DB_PATH
con = sqlite3.connect(DB_PATH)
cur = con.cursor()
cur.executescript(«»»
create table person(
firstname,
lastname,
age
);
create table book(
title,
author,
published
);
insert into book(title, author, published)
values (
'Dirk Gently''s Holistic Detective Agency',
'Douglas Adams',
1987
);
«»»)
con.commit()
con.close()
return DB_PATH
@pytest.fixture
def cursor(request):
class cursor:
def __init__(self, dbpath):
self.dbpath = dbpath
self.conn = sqlite3.connect(self.dbpath)
def table_exists(self, table):
cursor = self.conn.cursor()
cursor.execute(
«SELECT name FROM sqlite_master WHERE type = «table»»)
tables = cursor.fetchall()
for each in tables:
if table in each:
return True
return False
def getrows(self, table):
cursor = self.conn.cursor()
cursor.execute(«SELECT * FROM %s;» % (table))
return cursor.fetchall()
return cursor(request.config.DB_PATH)
# test.py
def test_1_db_exists(creator):
assert os.path.exists(creator)
@pytest.mark.parametrize(«table», [«person», «book»])
def test_2_check_scheme(table, creator, cursor):
result = cursor.table_exists(table)
assert result
@pytest.mark.parametrize(«table», [«book»])
def test_3_check_rows(table, creator, cursor):
assert cursor.getrows(table)
Спасибо документации питона за готовые примеры.
У такого подхода есть ряд недостатков.
1. В названии теста появляется нумерация — это лишняя мета информация. (Это может сказаться на тестовом отчете или билд логе CI сервера)
2. Название теста усложняется
3. Вставка нового элемента в середину приведет к смене нумерации всех последующих тестов
Вариант посложнее — маркеры
Свои маркеры мы реализовывать не будем, а используем готовый плагин pytest-ordering. В принципе вся реализация такого плагина составит не больше 100 строчек кода.
# test.py
@pytest.mark.order1
def test_db_exists(creator):
assert os.path.exists(creator)
@pytest.mark.order2
@pytest.mark.parametrize(«table», [«person», «book»])
def test_check_scheme(table, creator, cursor):
result = cursor.table_exists(table)
assert result
@pytest.mark.order3
@pytest.mark.parametrize(«table», [«book»])
def test_check_rows(table, creator, cursor):
assert cursor.getrows(table)
Наши тестовые функции преобразились, стали более понимаемы для чтения.
Из коробки плагин содержит создание цепочек выполнения, более понятные маркеры (pytest.mark.run('first'), pytest.mark.run('second')) и прочее. Более подробно в микро доку плагина.
Ссылки
[1] https://docs.python.org/2/library/sqlite3.html
[2] http://pytest-ordering.readthedocs.org/en/develop/
Автор: Евгений Курочкин
