Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions pytestomatio/testing/testItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
class TestItem:
def __init__(self, item: Item):
self.uid = uuid.uuid4()
self.id: str = TestItem.get_test_id(item)
self.type = self._get_test_type(item.function)
self.id: str = self.get_test_id(item)
self.title = self._get_pytest_title(item.name)
self.sync_title = self._get_sync_test_title(item)
self.resync_title = self._get_resync_test_title(item)
Expand All @@ -31,7 +31,7 @@ def __init__(self, item: Item):
self.docstring = inspect.getdoc(item.function)
self.class_name = item.cls.__name__ if item.cls else None
self.artifacts = item.stash.get("artifact_urls", [])

def to_dict(self) -> dict:
result = dict()
result['uid'] = str(self.uid)
Expand All @@ -51,8 +51,11 @@ def to_dict(self) -> dict:
def json(self) -> str:
return json.dumps(self.to_dict(), indent=4)

@staticmethod
def get_test_id(item: Item) -> str | None:
def get_test_id(self, item: Item) -> str | None:
if self.type == 'bdd':
for marker in item.iter_markers():
if marker.name.startswith('T'):
return '@' + marker.name
for marker in item.iter_markers(MARKER):
if marker.args:
return marker.args[0]
Expand Down Expand Up @@ -90,7 +93,7 @@ def _get_sync_test_title(self, item: Item) -> str:
test_name = self._resolve_parameter_key_in_test_name(item, test_name)
# Test id is present on already synced tests
# New test don't have testomatio test id.
test_id = TestItem.get_test_id(item)
test_id = self.id
if (test_id):
test_name = f'{test_name} {test_id}'
# ex. "User adds item to cart"
Expand Down Expand Up @@ -121,9 +124,9 @@ def _get_resync_test_title(self, name: str) -> str:
else:
return name

def _get_test_parameter_key(self, item: Item):
def _get_test_parameter_key(self, item: Item) -> list:
"""Return a list of parameter names for a given test item."""
param_names = set()
param_names = []

# 1) Look for @pytest.mark.parametrize
for mark in item.iter_markers('parametrize'):
Expand All @@ -133,21 +136,24 @@ def _get_test_parameter_key(self, item: Item):
arg_string = mark.args[0]
# If the string has commas, split it into multiple names
if ',' in arg_string:
param_names.update(name.strip() for name in arg_string.split(','))
param_names.extend([name.strip() for name in arg_string.split(',') if name not in param_names])
else:
param_names.add(arg_string.strip())
param_names.append(arg_string.strip())

# 2) Look for fixture parameterization (including dynamically generated)
# via callspec, which holds *all* final parameters for an item.
callspec = getattr(item, 'callspec', None)
if callspec:
# callspec.params is a dict: fixture_name -> parameter_value
# We only want fixture names, not the values.
param_names.update(callspec.params.keys())

# Return them as a list, or keep it as a set—whatever you prefer.
return list(param_names)

callspec_params = callspec.params
if self.type == 'bdd':
bdd_fixture_wrapper_name = '_pytest_bdd_example'
if bdd_fixture_wrapper_name in param_names:
param_names.remove(bdd_fixture_wrapper_name)
callspec_params = callspec.params.get(bdd_fixture_wrapper_name, {})
param_names.extend([name for name in callspec_params.keys() if name not in param_names])
return param_names

def _resolve_parameter_key_in_test_name(self, item: Item, test_name: str) -> str:
test_params = self._get_test_parameter_key(item)
Expand All @@ -174,7 +180,8 @@ def _resolve_parameter_value_in_test_name(self, item: Item, test_name: str) -> s

def repl(match):
key = match.group(1)
value = item.callspec.params.get(key, '')
value = item.callspec.params.get(key, '') if not self.type == 'bdd' else \
item.callspec.params.get('_pytest_bdd_example', {}).get(key, '')

string_value = self._to_string_value(value)
# TODO: handle "value with space" on testomatio BE https://github.com/testomatio/check-tests/issues/147
Expand Down
170 changes: 157 additions & 13 deletions tests/test_testing/test_TestItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def mock_item(self):
item.iter_markers.return_value = iter([])
return item

@pytest.fixture
def mock_bdd_item(self, mock_item):
mock_item.function.__scenario__ = True
return mock_item

@pytest.fixture
def mock_item_with_marker(self, mock_item):
"""Mock Item with testomatio marker"""
Expand Down Expand Up @@ -75,23 +80,81 @@ def test_init_with_class(self, mock_getsource, mock_item):

assert test_item.class_name == "TestClass"

def test_get_test_id_with_marker(self, mock_item):
@patch("inspect.getsource")
def test_get_test_id_with_marker(self, mock_source, mock_item):
"""Test get_test_id with marker"""
marker = Mock()
marker.name = 'testomatio'
marker.args = ["@T87654321"]
mock_item.iter_markers.return_value = iter([marker])

result = TestItem.get_test_id(mock_item)
test_item = TestItem(mock_item)
mock_item.iter_markers.return_value = iter([marker])
result = test_item.get_test_id(mock_item)

assert result == "@T87654321"
assert result == test_item.id

@patch("inspect.getsource")
def test_get_test_id_with_other_markers(self, mock_source, mock_item):
"""Test get_test_id with other marker"""
marker = Mock()
marker.name = 'other'
marker.args = ["@T87654321"]
markers = [marker]
mock_item.iter_markers.side_effect = lambda name=None: iter(
[m for m in markers if name is None or m.name == name])
test_item = TestItem(mock_item)
result = test_item.get_test_id(mock_item)

def test_get_test_id_without_marker(self, mock_item):
assert result is None
assert test_item.id is None

@patch("inspect.getsource")
def test_get_test_id_without_marker(self, mock_source, mock_item):
"""Test get_test_id without marker"""
mock_item.iter_markers.return_value = iter([])
test_item = TestItem(mock_item)
result = test_item.get_test_id(mock_item)

assert result is None
assert test_item.id is None

@patch("inspect.getsource")
def test_get_test_id_for_bdd_test(self, mock_source, mock_item):
"""Test get_test_id with correct marker for bdd test"""
marker = Mock()
marker.name = "T87654321"
mock_item.iter_markers.return_value = iter([marker])
mock_item.function.__scenario__ = True
test_item = TestItem(mock_item)
mock_item.iter_markers.return_value = iter([marker])
result = test_item.get_test_id(mock_item)
assert result == "@T87654321"
assert test_item.id == result

result = TestItem.get_test_id(mock_item)
@patch("inspect.getsource")
def test_get_test_id_for_bdd_test_with_other_marker(self, mock_source, mock_item):
"""Test get_test_id without correct marker for bdd test"""
marker = Mock()
marker.name = 'other_marker'
marker.args = ["@T87654321"]
mock_item.iter_markers.return_value = iter([marker])
mock_item.function.__scenario__ = True
test_item = TestItem(mock_item)
result = test_item.get_test_id(mock_item)
assert result is None
assert test_item.id is None

@patch("inspect.getsource")
def test_get_test_id_for_bdd_test_without_marker(self, mock_source, mock_item):
"""Test get_test_id without marker for bdd test"""
mock_item.iter_markers.return_value = iter([])
mock_item.function.__scenario__ = True
test_item = TestItem(mock_item)
result = test_item.get_test_id(mock_item)

assert result is None
assert test_item.id is None

def test_get_pytest_title_simple(self, mock_item):
"""Test _get_pytest_title without params"""
Expand Down Expand Up @@ -127,6 +190,17 @@ def test_get_test_parameter_key_no_params(self, mock_item):

assert result == []

@patch('inspect.getsource')
def test_get_test_parameter_key_no_params_for_bdd_test(self, mock_source, mock_bdd_item):
"""Test _get_test_parameter_key without params for bdd test"""
mock_bdd_item.iter_markers.return_value = iter([])
test_item = TestItem(mock_bdd_item)

result = test_item._get_test_parameter_key(mock_bdd_item)

assert test_item.type == 'bdd'
assert result == []

def test_get_test_parameter_key_with_parametrize(self, mock_item):
"""Test _get_test_parameter_key with @pytest.mark.parametrize"""
param_marker = Mock()
Expand All @@ -140,27 +214,68 @@ def test_get_test_parameter_key_with_parametrize(self, mock_item):

assert set(result) == {"param1", "param2"}

def test_get_test_parameter_key_with_callspec(self, mock_item):
@patch('inspect.getsource')
def test_get_test_parameter_key_with_callspec(self, mock_source, mock_item):
"""Test _get_test_parameter_key with callspec"""
mock_item.iter_markers.return_value = iter([])
mock_item.callspec = Mock()
mock_item.callspec.params = {"fixture1": "value1", "fixture2": "value2"}

test_item = TestItem.__new__(TestItem)
test_item = TestItem(mock_item)

result = test_item._get_test_parameter_key(mock_item)

assert set(result) == {"fixture1", "fixture2"}

def test_resolve_parameter_key_in_test_name(self, mock_item):
@patch('inspect.getsource')
def test_get_test_parameter_key_keeps_key_order(self, mock_source, mock_item):
"""Test _get_test_parameter_key keeps key order"""
mock_item.iter_markers.return_value = iter([])
mock_item.callspec = Mock()
mock_item.callspec.params = {"fixture1": "value1", "fixture2": "value2"}

test_item = TestItem(mock_item)

result = test_item._get_test_parameter_key(mock_item)

assert set(result) == {"fixture1", "fixture2"}
assert result[0] == 'fixture1'
assert result[1] == 'fixture2'

@patch('inspect.getsource')
def test_get_test_parameter_key_with_callspec_for_bdd_test(self, mock_source, mock_bdd_item):
"""Test _get_test_parameter_key with callspec for bdd test"""
mock_bdd_item.iter_markers.return_value = iter([])
mock_bdd_item.callspec = Mock()
mock_bdd_item.callspec.params = {'_pytest_bdd_example': {"fixture1": "value1", "fixture2": "value2"}}
test_item = TestItem(mock_bdd_item)

result = test_item._get_test_parameter_key(mock_bdd_item)

assert test_item.type == 'bdd'
assert set(result) == {"fixture1", "fixture2"}

@patch('inspect.getsource')
def test_resolve_parameter_key_in_test_name(self, mock_source, mock_item):
"""Test _resolve_parameter_key_in_test_name"""
test_item = TestItem.__new__(TestItem)
test_item = TestItem(mock_item)

with patch.object(test_item, '_get_test_parameter_key', return_value=["param1", "param2"]):
result = test_item._resolve_parameter_key_in_test_name(mock_item, "Test name[value]")

assert result == "Test name ${param1} ${param2}"

@patch('inspect.getsource')
def test_resolve_parameter_key_in_test_name_for_bdd_test(self, mock_source, mock_bdd_item):
"""Test _resolve_parameter_key_in_test_name for bdd test"""
test_item = TestItem(mock_bdd_item)

with patch.object(test_item, '_get_test_parameter_key', return_value=["param1", "param2"]):
result = test_item._resolve_parameter_key_in_test_name(mock_bdd_item, "Test name[value]")

assert test_item.type == 'bdd'
assert result == "Test name ${param1} ${param2}"

def test_resolve_parameter_key_no_params(self, mock_item):
"""Test _resolve_parameter_key_in_test_name without params"""
test_item = TestItem.__new__(TestItem)
Expand All @@ -170,6 +285,17 @@ def test_resolve_parameter_key_no_params(self, mock_item):

assert result == "Test name"

@patch('inspect.getsource')
def test_resolve_parameter_key_no_params_for_bdd_test(self, mock_source, mock_bdd_item):
"""Test _resolve_parameter_key_in_test_name without params for bdd test"""
test_item = TestItem(mock_bdd_item)

with patch.object(test_item, '_get_test_parameter_key', return_value=[]):
result = test_item._resolve_parameter_key_in_test_name(mock_bdd_item, "Test name")

assert test_item.type == 'bdd'
assert result == "Test name"

def test_to_string_value_various_types(self, mock_item):
"""Test _to_string_value with different types"""
test_item = TestItem.__new__(TestItem)
Expand Down Expand Up @@ -257,9 +383,10 @@ def test_str_and_repr(self, mock_getsource, mock_item_with_marker):
assert str_result == expected
assert repr_result == expected

def test_resolve_parameter_value_in_test_name(self, mock_item):
@patch('inspect.getsource')
def test_resolve_parameter_value_in_test_name(self, mock_source, mock_item):
"""Test _resolve_parameter_value_in_test_name method"""
test_item = TestItem.__new__(TestItem)
test_item = TestItem(mock_item)

mock_item.callspec = Mock()
mock_item.callspec.params = {"param1": "value1", "param2": "value with spaces"}
Expand All @@ -271,9 +398,26 @@ def test_resolve_parameter_value_in_test_name(self, mock_item):
assert "value1" in result
assert "value_with_spaces" in result

def test_resolve_parameter_value_no_callspec(self, mock_item):
@patch('inspect.getsource')
def test_resolve_parameter_value_in_test_name_for_bdd_test(self, mock_source, mock_bdd_item):
"""Test _resolve_parameter_value_in_test_name method for bdd test"""
test_item = TestItem(mock_bdd_item)

mock_bdd_item.callspec = Mock()
mock_bdd_item.callspec.params = {'_pytest_bdd_example': {"param1": "value1", "param2": "value with spaces"}}

with patch.object(test_item, '_get_test_parameter_key', return_value=["param1", "param2"]):
with patch.object(test_item, '_get_sync_test_title', return_value="Test ${param1} and ${param2}"):
result = test_item._resolve_parameter_value_in_test_name(mock_bdd_item, "Test name")

assert test_item.type == 'bdd'
assert "value1" in result
assert "value_with_spaces" in result

@patch("inspect.getsource")
def test_resolve_parameter_value_no_callspec(self, mock_source, mock_item):
"""Test _resolve_parameter_value_in_test_name without callspec"""
test_item = TestItem.__new__(TestItem)
test_item = TestItem(mock_item)
mock_item.callspec = None

with patch.object(test_item, '_get_test_parameter_key', return_value=["param1"]):
Expand Down
Loading