Initial commit: Requirements to Gherkin CLI Converter

This commit is contained in:
Bot
2026-02-02 12:15:36 +00:00
commit ec3ea3da33
29 changed files with 2803 additions and 0 deletions

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Tests package for nl2gherkin."""

116
tests/test_ambiguity.py Normal file
View File

@@ -0,0 +1,116 @@
"""Tests for the ambiguity detection module."""
import pytest
from nl2gherkin.nlp.ambiguity import AmbiguityDetector, AmbiguityType
class TestAmbiguityDetector:
"""Test cases for AmbiguityDetector."""
@pytest.fixture
def detector(self):
"""Create a detector instance."""
return AmbiguityDetector()
def test_pronoun_detection(self, detector):
"""Test detection of ambiguous pronouns."""
text = "When the user clicks it, the system should show the results"
result = detector.detect(text)
assert len(result) >= 0
def test_vague_quantifier_some(self, detector):
"""Test detection of 'some' quantifier."""
text = "The system should handle some requests efficiently"
result = detector.detect(text)
quantifier_types = [w.type for w in result]
assert AmbiguityType.VAGUE_QUANTIFIER in quantifier_types
def test_vague_quantifier_many(self, detector):
"""Test detection of 'many' quantifier."""
text = "Many users should be able to login"
result = detector.detect(text)
quantifier_types = [w.type for w in result]
assert AmbiguityType.VAGUE_QUANTIFIER in quantifier_types
def test_vague_quantifier_few(self, detector):
"""Test detection of 'few' quantifier."""
text = "Only few options are available"
result = detector.detect(text)
quantifier_types = [w.type for w in result]
assert AmbiguityType.VAGUE_QUANTIFIER in quantifier_types
def test_temporal_ambiguity(self, detector):
"""Test detection of temporal ambiguities."""
text = "The task should be completed soon"
result = detector.detect(text)
temporal_types = [w.type for w in result]
assert AmbiguityType.TEMPORAL in temporal_types
def test_missing_condition(self, detector):
"""Test detection of missing conditions."""
text = "The user must login to access the dashboard"
result = detector.detect(text)
condition_types = [w.type for w in result]
assert AmbiguityType.MISSING_CONDITION in condition_types
def test_passive_voice_detection(self, detector):
"""Test detection of passive voice."""
text = "The file was created by the system"
result = detector.detect(text)
voice_types = [w.type for w in result]
assert AmbiguityType.PASSIVE_VOICE in voice_types
def test_clear_requirement_no_warnings(self, detector):
"""Test that clear requirements have few warnings."""
text = "When the user clicks the submit button, the form data is validated on the server"
result = detector.detect(text)
assert len(result) <= 2
def test_warning_has_suggestion(self, detector):
"""Test that warnings include suggestions."""
text = "Some data should be processed"
result = detector.detect(text)
if result:
assert any(w.suggestion is not None for w in result)
def test_warning_severity_levels(self, detector):
"""Test that warnings have severity levels."""
text = "The report was generated"
result = detector.detect(text)
for warning in result:
assert warning.severity in ["low", "medium", "high"]
def test_to_dict_method(self, detector):
"""Test that warnings can be converted to dict."""
text = "It should work"
result = detector.detect(text)
if result:
warning_dict = result[0].to_dict()
assert "type" in warning_dict
assert "message" in warning_dict
assert "suggestion" in warning_dict
def test_multiple_ambiguities(self, detector):
"""Test detection of multiple ambiguities in one text."""
text = "When it happens, some users might see many results eventually"
result = detector.detect(text)
assert len(result) >= 2
def test_empty_text(self, detector):
"""Test handling of empty text."""
result = detector.detect("")
assert isinstance(result, list)
assert len(result) == 0

119
tests/test_analyzer.py Normal file
View File

@@ -0,0 +1,119 @@
"""Tests for the NLP analyzer module."""
import pytest
from nl2gherkin.nlp.analyzer import ActionType, ActorType, NLPAnalyzer
class TestNLPAnalyzer:
"""Test cases for NLPAnalyzer."""
@pytest.fixture
def analyzer(self):
"""Create an analyzer instance."""
return NLPAnalyzer()
def test_user_story_parsing(self, analyzer):
"""Test parsing classic user story format."""
text = "As a user, I want to login so that I can access my account"
result = analyzer.analyze(text)
assert result.actor == "user"
assert "login" in result.action.lower()
assert result.benefit is not None
def test_user_story_with_admin(self, analyzer):
"""Test parsing admin user story."""
text = "As an administrator, I want to manage users so that I can control access"
result = analyzer.analyze(text)
assert result.actor == "administrator"
assert "manage" in result.action.lower() or result.action is not None
assert result.benefit is not None
def test_simple_requirement(self, analyzer):
"""Test parsing a simple requirement without user story format."""
text = "The system should send an email notification"
result = analyzer.analyze(text)
assert result.action is not None or "send" in result.raw_text.lower()
def test_action_extraction_create(self, analyzer):
"""Test action extraction for create operations."""
text = "As a user, I want to create a new document"
result = analyzer.analyze(text)
assert result.action_type in [ActionType.CREATE, ActionType.UNKNOWN]
def test_action_extraction_read(self, analyzer):
"""Test action extraction for read operations."""
text = "As a user, I want to view my profile"
result = analyzer.analyze(text)
assert result.action_type in [ActionType.READ, ActionType.UNKNOWN]
def test_action_extraction_delete(self, analyzer):
"""Test action extraction for delete operations."""
text = "As an admin, I want to delete the account"
result = analyzer.analyze(text)
assert result.action_type in [ActionType.DELETE, ActionType.UNKNOWN]
def test_actor_type_user(self, analyzer):
"""Test actor type detection for user."""
text = "As a customer, I want to place an order"
result = analyzer.analyze(text)
assert result.actor_type in [ActorType.USER, ActorType.UNKNOWN]
def test_actor_type_admin(self, analyzer):
"""Test actor type detection for admin."""
text = "As an administrator, I want to view logs"
result = analyzer.analyze(text)
assert result.actor_type in [ActorType.ADMIN, ActorType.UNKNOWN]
def test_actor_type_system(self, analyzer):
"""Test actor type detection for system."""
text = "The system should validate the input"
result = analyzer.analyze(text)
assert result.actor_type in [ActorType.SYSTEM, ActorType.UNKNOWN]
def test_condition_extraction(self, analyzer):
"""Test condition extraction from requirements."""
text = "When the user clicks submit, the form should be validated"
result = analyzer.analyze(text)
assert result.condition is not None or "click" in result.raw_text.lower()
def test_variable_extraction(self, analyzer):
"""Test variable/parameter extraction."""
text = "As a user, I want to search for <product_name> so that I can find it"
result = analyzer.analyze(text)
assert "product_name" in result.variables
def test_to_dict_method(self, analyzer):
"""Test that to_dict returns a proper dictionary."""
text = "As a user, I want to login"
result = analyzer.analyze(text)
result_dict = result.to_dict()
assert isinstance(result_dict, dict)
assert "actor" in result_dict
assert "action" in result_dict
assert "actor_type" in result_dict
def test_empty_input(self, analyzer):
"""Test handling of empty input."""
result = analyzer.analyze("")
assert result.raw_text == ""
def test_ambiguity_detection_call(self, analyzer):
"""Test that ambiguity detection can be called."""
text = "As a user, I want to do something"
result = analyzer.analyze_ambiguity(text)
assert isinstance(result, list)

127
tests/test_cli.py Normal file
View File

@@ -0,0 +1,127 @@
"""Tests for the CLI module."""
import pytest
from click.testing import CliRunner
from nl2gherkin.cli.commands import cli, convert, validate
class TestCLI:
"""Test cases for CLI commands."""
@pytest.fixture
def runner(self):
"""Create a CLI runner."""
return CliRunner()
def test_cli_help(self, runner):
"""Test CLI help command."""
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "convert" in result.output
assert "interactive" in result.output
assert "validate" in result.output
def test_convert_help(self, runner):
"""Test convert command help."""
result = runner.invoke(convert, ["--help"])
assert result.exit_code == 0
assert "INPUT_FILE" in result.output
assert "--output" in result.output
assert "--framework" in result.output
assert "--validate" in result.output
def test_convert_requires_input_file(self, runner):
"""Test that convert requires an input file."""
result = runner.invoke(convert, [])
assert result.exit_code != 0
assert "INPUT_FILE" in result.output or "Missing argument" in result.output
def test_validate_help(self, runner):
"""Test validate command help."""
result = runner.invoke(validate, ["--help"])
assert result.exit_code == 0
assert "GHERKIN_FILE" in result.output
def test_convert_with_sample_requirement(self, runner, tmp_path):
"""Test converting a sample requirement file."""
req_file = tmp_path / "requirements.txt"
req_file.write_text("As a user, I want to login so that I can access my account")
result = runner.invoke(convert, [str(req_file), "--no-validate"])
assert result.exit_code == 0
assert "Feature:" in result.output
assert "Given" in result.output
assert "When" in result.output
assert "Then" in result.output
def test_convert_with_output_file(self, runner, tmp_path):
"""Test converting with output file option."""
req_file = tmp_path / "requirements.txt"
req_file.write_text("As a user, I want to search for products")
output_file = tmp_path / "output.feature"
result = runner.invoke(convert, [
str(req_file),
"--output", str(output_file),
"--no-validate",
])
assert result.exit_code == 0
assert output_file.exists()
content = output_file.read_text()
assert "Feature:" in content
def test_convert_with_different_frameworks(self, runner, tmp_path):
"""Test converting with different BDD frameworks."""
req_file = tmp_path / "requirements.txt"
req_file.write_text("As a user, I want to login")
for framework in ["cucumber", "behave", "pytest-bdd"]:
result = runner.invoke(convert, [
str(req_file),
"--framework", framework,
"--no-validate",
])
assert result.exit_code == 0, f"Failed for framework: {framework}"
def test_validate_valid_gherkin(self, runner, tmp_path):
"""Test validating a valid Gherkin file."""
gherkin_file = tmp_path / "test.feature"
gherkin_file.write_text("""Feature: Test
Scenario: A test scenario
Given a setup
When an action occurs
Then an expected result
""")
result = runner.invoke(validate, [str(gherkin_file)])
assert result.exit_code == 0
def test_ambiguity_check_flag(self, runner, tmp_path):
"""Test that ambiguity check flag produces warnings."""
req_file = tmp_path / "requirements.txt"
req_file.write_text("As a user, I want to do something with some data")
result = runner.invoke(convert, [
str(req_file),
"--ambiguity-check",
"--no-validate",
])
assert result.exit_code == 0
def test_interactive_command_exists(self, runner):
"""Test that interactive command exists."""
result = runner.invoke(cli, ["interactive", "--help"])
assert result.exit_code == 0
assert "--framework" in result.output

178
tests/test_generator.py Normal file
View File

@@ -0,0 +1,178 @@
"""Tests for the Gherkin generator module."""
import pytest
from nl2gherkin.gherkin.generator import (
GherkinFeature,
GherkinGenerator,
GherkinScenario,
ScenarioType,
)
from nl2gherkin.nlp.analyzer import ActionType, ActorType, RequirementAnalysis
class TestGherkinGenerator:
"""Test cases for GherkinGenerator."""
@pytest.fixture
def generator(self):
"""Create a generator instance."""
return GherkinGenerator()
@pytest.fixture
def analysis(self):
"""Create a sample analysis."""
return RequirementAnalysis(
raw_text="As a user, I want to login",
actor="user",
actor_type=ActorType.USER,
action="login",
action_type=ActionType.LOGIN,
target="account",
)
def test_generate_simple_scenario(self, generator, analysis):
"""Test generating a simple scenario."""
result = generator.generate(analysis)
assert "Feature:" in result
assert "Scenario:" in result
assert "Given" in result
assert "When" in result
assert "Then" in result
def test_feature_name_from_actor_and_action(self, generator, analysis):
"""Test feature name is derived from actor and action."""
result = generator.generate(analysis)
assert "user login" in result.lower()
def test_scenario_name_generation(self, generator, analysis):
"""Test scenario name is properly generated."""
scenario = generator.generate_scenario(analysis)
assert scenario.name is not None
assert len(scenario.name) > 0
def test_scenario_type_regular(self, generator, analysis):
"""Test regular scenario type."""
scenario = generator.generate_scenario(analysis)
assert scenario.scenario_type == ScenarioType.SCENARIO
def test_scenario_type_outline_with_variables(self, generator):
"""Test scenario outline type with variables."""
analysis = RequirementAnalysis(
raw_text="As a user, I want to search for <product>",
actor="user",
action="search",
variables={"product": "string"},
examples=["product"],
)
scenario = generator.generate_scenario(analysis)
assert scenario.scenario_type == ScenarioType.SCENARIO_OUTLINE
def test_examples_table_generation(self, generator):
"""Test Examples table is generated for outlines."""
analysis = RequirementAnalysis(
raw_text="As a user, I want to search for <product>",
actor="user",
action="search",
variables={"product": "string"},
examples=["product"],
)
result = generator.generate(analysis)
assert "Examples:" in result
assert "| product |" in result
def test_steps_have_correct_keywords(self, generator, analysis):
"""Test that steps have correct Gherkin keywords."""
scenario = generator.generate_scenario(analysis)
keywords = [step.keyword for step in scenario.steps]
assert "Given" in keywords
assert "When" in keywords
assert "Then" in keywords
def test_benefit_in_description(self, generator):
"""Test that benefit is included in description."""
analysis = RequirementAnalysis(
raw_text="As a user, I want to login so that I can access my account",
actor="user",
action="login",
benefit="I can access my account",
)
result = generator.generate(analysis)
assert "can access my account" in result
def test_condition_as_given(self, generator):
"""Test that conditions are converted to Given steps."""
analysis = RequirementAnalysis(
raw_text="When the user is logged in",
actor="user",
action="view",
target="dashboard",
condition="the user is logged in",
)
scenario = generator.generate_scenario(analysis)
given_steps = [s for s in scenario.steps if s.keyword == "Given"]
assert len(given_steps) >= 1
def test_read_action_display_then_step(self, generator):
"""Test that read actions use 'displayed' in Then step."""
analysis = RequirementAnalysis(
raw_text="As a user, I want to view my profile",
actor="user",
action="view",
action_type=ActionType.READ,
target="profile",
)
scenario = generator.generate_scenario(analysis)
then_steps = [s for s in scenario.steps if s.keyword == "Then"]
assert len(then_steps) > 0
assert "displayed" in then_steps[0].text.lower()
def test_empty_analysis(self, generator):
"""Test handling of empty analysis."""
analysis = RequirementAnalysis(raw_text="")
result = generator.generate(analysis)
assert "Feature:" in result
def test_render_feature_structure(self, generator, analysis):
"""Test that rendered feature has proper structure."""
feature = generator._create_feature(analysis)
assert isinstance(feature, GherkinFeature)
assert feature.name is not None
assert len(feature.scenarios) == 1
assert isinstance(feature.scenarios[0], GherkinScenario)
def test_multiple_scenarios(self, generator):
"""Test generating multiple scenarios."""
analysis1 = RequirementAnalysis(
raw_text="As a user, I want to login",
actor="user",
action="login",
)
analysis2 = RequirementAnalysis(
raw_text="As a user, I want to logout",
actor="user",
action="logout",
)
feature1 = generator._create_feature(analysis1)
feature2 = generator._create_feature(analysis2)
assert len(feature1.scenarios) == 1
assert len(feature2.scenarios) == 1