add custom pytest collector for BDD feature files

This commit is contained in:
Sarah Hoffmann
2025-10-30 17:44:17 +01:00
parent 9a13b62fb9
commit 55547723bf
5 changed files with 71 additions and 7 deletions

View File

@@ -52,6 +52,15 @@ To run the functional tests, do
pytest test/bdd pytest test/bdd
You can run a single feature file using expression matching:
pytest test/bdd -k osm2pgsql/import/entrances.feature
This even works for running single tests by adding the line number of the
scenario header like that:
pytest test/bdd -k 'osm2pgsql/import/entrances.feature and L4'
The BDD tests create databases for the tests. You can set name of the databases The BDD tests create databases for the tests. You can set name of the databases
through configuration variables in your `pytest.ini`: through configuration variables in your `pytest.ini`:

View File

@@ -9,6 +9,7 @@ Fixtures for BDD test steps
""" """
import sys import sys
import json import json
import re
from pathlib import Path from pathlib import Path
import psycopg import psycopg
@@ -20,7 +21,8 @@ sys.path.insert(0, str(SRC_DIR / 'src'))
import pytest import pytest
from pytest_bdd.parsers import re as step_parse from pytest_bdd.parsers import re as step_parse
from pytest_bdd import given, when, then from pytest_bdd import given, when, then, scenario
from pytest_bdd.feature import get_features
pytest.register_assert_rewrite('utils') pytest.register_assert_rewrite('utils')
@@ -373,3 +375,56 @@ def check_place_missing_lines(db_conn, table, osm_type, osm_id, osm_class):
with db_conn.cursor() as cur: with db_conn.cursor() as cur:
assert cur.execute(sql, params).fetchone()[0] == 0 assert cur.execute(sql, params).fetchone()[0] == 0
def pytest_pycollect_makemodule(module_path, parent):
return BddTestCollector.from_parent(parent, path=module_path)
class BddTestCollector(pytest.Module):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def collect(self):
for item in super().collect():
yield item
if hasattr(self.obj, 'PYTEST_BDD_SCENARIOS'):
for path in self.obj.PYTEST_BDD_SCENARIOS:
for feature in get_features([str(Path(self.path.parent, path).resolve())]):
yield FeatureFile.from_parent(self,
name=str(Path(path, feature.rel_filename)),
path=Path(feature.filename),
feature=feature)
# borrowed from pytest-bdd: src/pytest_bdd/scenario.py
def make_python_name(string: str) -> str:
"""Make python attribute name out of a given string."""
string = re.sub(r"\W", "", string.replace(" ", "_"))
return re.sub(r"^\d+_*", "", string).lower()
class FeatureFile(pytest.File):
class obj:
pass
def __init__(self, feature, **kwargs):
self.feature = feature
super().__init__(**kwargs)
def collect(self):
for sname, sobject in self.feature.scenarios.items():
class_name = f"L{sobject.line_number}"
test_name = "test_" + make_python_name(sname)
@scenario(self.feature.filename, sname)
def _test():
pass
tclass = type(class_name, (),
{test_name: staticmethod(_test)})
setattr(self.obj, class_name, tclass)
yield pytest.Class.from_parent(self, name=class_name, obj=tclass)

View File

@@ -15,7 +15,7 @@ import xml.etree.ElementTree as ET
import pytest import pytest
from pytest_bdd.parsers import re as step_parse from pytest_bdd.parsers import re as step_parse
from pytest_bdd import scenarios, when, given, then from pytest_bdd import when, given, then
from nominatim_db import cli from nominatim_db import cli
from nominatim_db.config import Configuration from nominatim_db.config import Configuration
@@ -150,4 +150,4 @@ def parse_api_json_response(api_response, fmt, num):
return result return result
scenarios('features/api') PYTEST_BDD_SCENARIOS = ['features/api']

View File

@@ -15,7 +15,7 @@ import re
import psycopg import psycopg
import pytest import pytest
from pytest_bdd import scenarios, when, then, given from pytest_bdd import when, then, given
from pytest_bdd.parsers import re as step_parse from pytest_bdd.parsers import re as step_parse
from utils.place_inserter import PlaceColumn from utils.place_inserter import PlaceColumn
@@ -276,4 +276,4 @@ def then_check_interpolation_table_negative(db_conn, oid):
assert cur.fetchone()[0] == 0 assert cur.fetchone()[0] == 0
scenarios('features/db') PYTEST_BDD_SCENARIOS = ['features/db']

View File

@@ -11,7 +11,7 @@ import asyncio
import random import random
import pytest import pytest
from pytest_bdd import scenarios, when, then, given from pytest_bdd import when, then, given
from pytest_bdd.parsers import re as step_parse from pytest_bdd.parsers import re as step_parse
from nominatim_db import cli from nominatim_db import cli
@@ -106,4 +106,4 @@ def check_place_content(db_conn, datatable, node_grid, table, exact):
check_table_content(db_conn, table, datatable, grid=node_grid, exact=bool(exact)) check_table_content(db_conn, table, datatable, grid=node_grid, exact=bool(exact))
scenarios('features/osm2pgsql') PYTEST_BDD_SCENARIOS = ['features/osm2pgsql']