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
- 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
237 lines
7.7 KiB
Python
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)
|