Summary
Extract test functions from Python test files using tree-sitter. Produces DiscoveredItem(kind=TEST_FUNCTION) for each found test.
Depends on: #124, #125, #126
New file
src/specleft/discovery/miners/python/tests.py
import uuid
from specleft.discovery.models import SupportedLanguage, MinerResult, DiscoveredItem, ItemKind, TestFunctionMeta
from specleft.discovery.context import MinerContext
class PythonTestMiner:
miner_id = uuid.UUID("a7b21db5-0d22-41be-9902-7c725e63892e")
name = "python_test_functions"
languages = frozenset({SupportedLanguage.PYTHON})
def mine(self, ctx: MinerContext) -> MinerResult: ...
Detection rules
File targeting: use ctx.file_index.files_matching("test_*.py", "*_test.py") — do NOT walk the filesystem directly.
Tree-sitter queries (via ctx.registry.parse(file_path)):
- Top-level functions where name starts with
test_
- Methods inside a class where the class name starts with
Test
Framework detection: read from ctx.frameworks[SupportedLanguage.PYTHON] — do NOT re-detect. The FrameworkDetector has already resolved this.
Typed metadata
Each item's metadata dict must conform to TestFunctionMeta:
TestFunctionMeta(
framework = ctx.frameworks.get(SupportedLanguage.PYTHON, ["unknown"])[0],
class_name = "TestLogin" | None,
decorators = ["pytest.mark.parametrize"],
has_docstring = True,
docstring = "Test that valid credentials...",
is_parametrized = True,
)
Pass meta.model_dump() as the metadata dict when constructing DiscoveredItem.
name: raw function name (e.g. test_valid_credentials)
language: SupportedLanguage.PYTHON
confidence: 0.9 for test_ prefix + known framework; 0.7 otherwise
Error handling
Return MinerResult(error_kind=MinerErrorKind.PARSE_ERROR, error="...") for files that fail to parse, not exceptions. Continue processing remaining files.
Acceptance criteria
Summary
Extract test functions from Python test files using tree-sitter. Produces
DiscoveredItem(kind=TEST_FUNCTION)for each found test.Depends on: #124, #125, #126
New file
src/specleft/discovery/miners/python/tests.pyDetection rules
File targeting: use
ctx.file_index.files_matching("test_*.py", "*_test.py")— do NOT walk the filesystem directly.Tree-sitter queries (via
ctx.registry.parse(file_path)):test_TestFramework detection: read from
ctx.frameworks[SupportedLanguage.PYTHON]— do NOT re-detect. TheFrameworkDetectorhas already resolved this.Typed metadata
Each item's
metadatadict must conform toTestFunctionMeta:Pass
meta.model_dump()as themetadatadict when constructingDiscoveredItem.name: raw function name (e.g.test_valid_credentials)language:SupportedLanguage.PYTHONconfidence:0.9fortest_prefix + known framework;0.7otherwiseError handling
Return
MinerResult(error_kind=MinerErrorKind.PARSE_ERROR, error="...")for files that fail to parse, not exceptions. Continue processing remaining files.Acceptance criteria
TestCase), miner returns exactly 3 itemslanguage=SupportedLanguage.PYTHONmetadatavalidates againstTestFunctionMeta(noValidationError)is_parametrized=Trueset correctly for the parametrized functionclass_namepopulated for theTestCasemethod,Nonefor top-level functionsctx.file_index.files_matching()— does not walk filesystemctx.frameworks— does not re-detect frameworkerror_kind=PARSE_ERRORon the result, not exceptionstests/discovery/miners/test_python_tests.pyusing fixtures fromtests/fixtures/discovery/features/feature-spec-discovery.mdto cover the functionality introduced by this issue