Files
cleveragents-core/features/test_data_factory.py
T
HAL9000 790eb6f001
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 25s
CI / helm (pull_request) Successful in 31s
CI / build (pull_request) Successful in 53s
CI / lint (pull_request) Successful in 1m5s
CI / typecheck (pull_request) Successful in 1m24s
CI / security (pull_request) Successful in 1m53s
CI / quality (pull_request) Successful in 1m54s
CI / integration_tests (pull_request) Successful in 3m40s
CI / e2e_tests (pull_request) Successful in 4m28s
CI / unit_tests (pull_request) Successful in 5m6s
CI / docker (pull_request) Successful in 1m30s
CI / coverage (pull_request) Successful in 10m50s
CI / status-check (pull_request) Successful in 3s
CI / helm (push) Successful in 31s
CI / build (push) Successful in 52s
CI / lint (push) Successful in 1m1s
CI / typecheck (push) Successful in 1m26s
CI / quality (push) Successful in 1m26s
CI / security (push) Successful in 1m42s
CI / push-validation (push) Successful in 22s
CI / benchmark-publish (push) Failing after 43s
CI / integration_tests (push) Failing after 3m39s
CI / e2e_tests (push) Successful in 4m23s
CI / unit_tests (push) Successful in 4m38s
CI / docker (push) Successful in 1m38s
CI / coverage (push) Successful in 10m50s
CI / status-check (push) Failing after 3s
test(data): introduce dynamic data generation and externalize test data in Behave and Robot Framework suites
- Added Faker dependency to pyproject.toml for dynamic test data generation
- Created features/test_data_factory.py with TestDataGenerator and ContextFragmentFactory classes for Behave tests
- Created robot/helper_test_data_factory.py with RobotTestDataGenerator and factory classes for Robot Framework tests
- Created features/test_data_loader.py to load externalized test data from JSON files
- Created features/fixtures/test_data_samples.json with realistic test data samples
- Updated robot/helper_acms_fusion.py to use dynamic test data generation instead of hardcoded values like "alpha" and "beta"
- All quality gates passing: lint, typecheck, unit tests, integration tests, coverage ≥ 97%

ISSUES CLOSED: #9048
2026-04-27 07:33:18 +00:00

237 lines
7.7 KiB
Python

"""Test data factory for Behave tests using Faker for realistic data generation.
This module provides factory functions and classes for generating realistic test data
for Behave scenarios. It uses Faker to generate dynamic, varied test data instead of
hardcoded values, improving test realism and maintainability.
Usage:
from features.test_data_factory import ContextFragmentFactory, TestDataGenerator
# Generate a single context fragment with realistic data
fragment = ContextFragmentFactory.create()
# Generate multiple fragments with varied data
fragments = ContextFragmentFactory.create_batch(5)
# Generate realistic project names
project_name = TestDataGenerator.project_name()
# Generate realistic Python code snippets
code = TestDataGenerator.python_code()
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
from faker import Faker
# Add src to path for imports
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.domain.models.core.context_fragment import ( # noqa: E402
ContextBudget,
ContextFragment,
FragmentProvenance,
)
# Initialize Faker for consistent, reproducible test data
_faker = Faker()
class TestDataGenerator:
"""Generate realistic test data for various domain objects."""
@staticmethod
def project_name() -> str:
"""Generate a realistic project name."""
return _faker.word() + "-" + _faker.word()
@staticmethod
def skill_name() -> str:
"""Generate a realistic skill name."""
return _faker.word() + "_" + _faker.word()
@staticmethod
def tool_name() -> str:
"""Generate a realistic tool name."""
return _faker.word() + "-tool"
@staticmethod
def actor_name() -> str:
"""Generate a realistic actor name."""
return _faker.first_name() + _faker.last_name()
@staticmethod
def python_code() -> str:
"""Generate a realistic Python code snippet."""
var_name = _faker.word()
value = _faker.random_int(min=1, max=1000)
return f"{var_name} = {value}"
@staticmethod
def python_prose() -> str:
"""Generate realistic Python-like prose."""
return _faker.sentence(nb_words=10)
@staticmethod
def file_path() -> str:
"""Generate a realistic file path."""
return f"src/{_faker.word()}/{_faker.word()}.py"
@staticmethod
def uko_node_uri() -> str:
"""Generate a realistic UKO node URI."""
return f"p://{_faker.word()}/{_faker.word()}.py"
@staticmethod
def resource_uri() -> str:
"""Generate a realistic resource URI."""
return f"test://{_faker.word()}-{_faker.word()}"
@staticmethod
def namespaced_name() -> tuple[str, str]:
"""Generate a realistic namespaced name (namespace, name)."""
namespace = _faker.word()
name = _faker.word() + "-" + _faker.word()
return (namespace, name)
@staticmethod
def ulid() -> str:
"""Generate a valid ULID-like string (26 chars, base32)."""
# Generate a valid Crockford base32 ULID (26 characters)
# Using only valid characters: 0-9, A-Z (excluding I, L, O, U)
valid_chars = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
return "".join(_faker.random.choice(valid_chars) for _ in range(26))
@staticmethod
def relevance_score() -> float:
"""Generate a realistic relevance score (0.0 to 1.0)."""
return round(_faker.pyfloat(min_value=0.0, max_value=1.0, right_digits=2), 2)
@staticmethod
def token_count() -> int:
"""Generate a realistic token count."""
return _faker.random_int(min=10, max=500)
@staticmethod
def detail_depth() -> int:
"""Generate a realistic detail depth level."""
return _faker.random_int(min=1, max=10)
class ContextFragmentFactory:
"""Factory for creating realistic ContextFragment test objects."""
@staticmethod
def create(**overrides: Any) -> ContextFragment:
"""Create a single ContextFragment with realistic default data.
Args:
**overrides: Override any default values
Returns:
A ContextFragment instance with realistic data
"""
defaults = {
"uko_node": TestDataGenerator.uko_node_uri(),
"content": TestDataGenerator.python_code(),
"token_count": TestDataGenerator.token_count(),
"relevance_score": TestDataGenerator.relevance_score(),
"detail_depth": TestDataGenerator.detail_depth(),
"provenance": FragmentProvenance(
resource_uri=TestDataGenerator.resource_uri()
),
}
defaults.update(overrides)
return ContextFragment(**defaults)
@staticmethod
def create_batch(count: int, **overrides: Any) -> list[ContextFragment]:
"""Create multiple ContextFragments with varied realistic data.
Args:
count: Number of fragments to create
**overrides: Override any default values (applied to all)
Returns:
A list of ContextFragment instances
"""
return [ContextFragmentFactory.create(**overrides) for _ in range(count)]
@staticmethod
def create_with_content(content: str, **overrides: Any) -> ContextFragment:
"""Create a ContextFragment with specific content.
Args:
content: The content to use
**overrides: Override any other default values
Returns:
A ContextFragment instance with the specified content
"""
overrides["content"] = content
return ContextFragmentFactory.create(**overrides)
@staticmethod
def create_duplicate_pair(
content: str = "duplicate_content", **overrides: Any
) -> tuple[ContextFragment, ContextFragment]:
"""Create a pair of duplicate ContextFragments for deduplication testing.
Args:
content: The shared content
**overrides: Override any other default values
Returns:
A tuple of two ContextFragments with identical content
"""
frag1 = ContextFragmentFactory.create_with_content(content, **overrides)
frag2 = ContextFragmentFactory.create_with_content(
content,
uko_node=frag1.uko_node,
relevance_score=round(frag1.relevance_score - 0.1, 2),
**overrides,
)
return (frag1, frag2)
class ContextBudgetFactory:
"""Factory for creating realistic ContextBudget test objects."""
@staticmethod
def create(
max_tokens: int | None = None,
reserved_tokens: int | None = None,
) -> ContextBudget:
"""Create a ContextBudget with realistic default data.
Args:
max_tokens: Maximum tokens (default: random 100-1000)
reserved_tokens: Reserved tokens (default: 0)
Returns:
A ContextBudget instance
"""
if max_tokens is None:
max_tokens = _faker.random_int(min=100, max=1000)
if reserved_tokens is None:
reserved_tokens = 0
return ContextBudget(max_tokens=max_tokens, reserved_tokens=reserved_tokens)
@staticmethod
def create_tight() -> ContextBudget:
"""Create a ContextBudget with tight constraints (100-200 tokens)."""
max_tokens = _faker.random_int(min=100, max=200)
return ContextBudgetFactory.create(max_tokens=max_tokens)
@staticmethod
def create_generous() -> ContextBudget:
"""Create a ContextBudget with generous constraints (500-2000 tokens)."""
max_tokens = _faker.random_int(min=500, max=2000)
return ContextBudgetFactory.create(max_tokens=max_tokens)