单元测试和改进
This commit is contained in:
414
tests/test_reactor.py
Normal file
414
tests/test_reactor.py
Normal file
@@ -0,0 +1,414 @@
|
||||
"""
|
||||
Unit tests for reactor modules: Phaser, Procession, Fission, States
|
||||
"""
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
|
||||
from src.heurams.kernel.reactor.phaser import Phaser
|
||||
from src.heurams.kernel.reactor.procession import Procession
|
||||
from src.heurams.kernel.reactor.fission import Fission
|
||||
from src.heurams.kernel.reactor.states import States
|
||||
from src.heurams.kernel.particles.atom import Atom
|
||||
from src.heurams.kernel.particles.nucleon import Nucleon
|
||||
from src.heurams.kernel.particles.electron import Electron
|
||||
from src.heurams.kernel.particles.orbital import Orbital
|
||||
|
||||
|
||||
class TestStates:
|
||||
"""Test cases for States enum."""
|
||||
|
||||
def test_states_enum_values(self):
|
||||
"""Test that States enum has correct values."""
|
||||
assert States.IDLE.value == "idle"
|
||||
assert States.LEARNING.value == "learning"
|
||||
assert States.REVIEW.value == "review"
|
||||
assert States.FINISHED.value == "finished"
|
||||
|
||||
def test_states_enum_membership(self):
|
||||
"""Test States enum membership."""
|
||||
assert isinstance(States.IDLE, Enum)
|
||||
assert States.LEARNING in States
|
||||
assert States.REVIEW in States
|
||||
assert States.FINISHED in States
|
||||
|
||||
|
||||
class TestPhaser:
|
||||
"""Test cases for Phaser class."""
|
||||
|
||||
def test_phaser_creation(self):
|
||||
"""Test Phaser creation."""
|
||||
phaser = Phaser()
|
||||
|
||||
assert phaser.current_state == States.IDLE
|
||||
assert phaser.atom is None
|
||||
assert phaser.puzzle is None
|
||||
|
||||
def test_phaser_initialize(self):
|
||||
"""Test Phaser initialization with atom."""
|
||||
phaser = Phaser()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
phaser.initialize(atom)
|
||||
|
||||
assert phaser.atom == atom
|
||||
assert phaser.current_state == States.LEARNING
|
||||
|
||||
def test_phaser_transition_to_review(self):
|
||||
"""Test transition to review state."""
|
||||
phaser = Phaser()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
phaser.initialize(atom)
|
||||
phaser.transition_to_review()
|
||||
|
||||
assert phaser.current_state == States.REVIEW
|
||||
|
||||
def test_phaser_transition_to_finished(self):
|
||||
"""Test transition to finished state."""
|
||||
phaser = Phaser()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
phaser.initialize(atom)
|
||||
phaser.transition_to_finished()
|
||||
|
||||
assert phaser.current_state == States.FINISHED
|
||||
|
||||
def test_phaser_reset(self):
|
||||
"""Test Phaser reset."""
|
||||
phaser = Phaser()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
phaser.initialize(atom)
|
||||
phaser.transition_to_review()
|
||||
phaser.reset()
|
||||
|
||||
assert phaser.current_state == States.IDLE
|
||||
assert phaser.atom is None
|
||||
assert phaser.puzzle is None
|
||||
|
||||
def test_phaser_set_puzzle(self):
|
||||
"""Test setting puzzle in Phaser."""
|
||||
phaser = Phaser()
|
||||
test_puzzle = {"question": "Test?", "answer": "Test", "type": "test"}
|
||||
|
||||
phaser.set_puzzle(test_puzzle)
|
||||
|
||||
assert phaser.puzzle == test_puzzle
|
||||
|
||||
def test_phaser_validation(self):
|
||||
"""Test input validation for Phaser."""
|
||||
phaser = Phaser()
|
||||
|
||||
# Test initialize with None
|
||||
with pytest.raises(TypeError):
|
||||
phaser.initialize(None)
|
||||
|
||||
# Test initialize with invalid type
|
||||
with pytest.raises(TypeError):
|
||||
phaser.initialize("not an atom")
|
||||
|
||||
|
||||
class TestProcession:
|
||||
"""Test cases for Procession class."""
|
||||
|
||||
def test_procession_creation(self):
|
||||
"""Test Procession creation."""
|
||||
procession = Procession()
|
||||
|
||||
assert procession.queue == []
|
||||
assert procession.current_index == 0
|
||||
|
||||
def test_procession_add_atom(self):
|
||||
"""Test adding atom to procession."""
|
||||
procession = Procession()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
procession.add_atom(atom)
|
||||
|
||||
assert len(procession.queue) == 1
|
||||
assert procession.queue[0] == atom
|
||||
|
||||
def test_procession_add_multiple_atoms(self):
|
||||
"""Test adding multiple atoms to procession."""
|
||||
procession = Procession()
|
||||
atoms = []
|
||||
for i in range(3):
|
||||
nucleon = Nucleon(content=f"Test{i}", answer=f"Answer{i}")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
atoms.append(atom)
|
||||
|
||||
for atom in atoms:
|
||||
procession.add_atom(atom)
|
||||
|
||||
assert len(procession.queue) == 3
|
||||
assert procession.queue == atoms
|
||||
|
||||
def test_procession_get_current_atom(self):
|
||||
"""Test getting current atom."""
|
||||
procession = Procession()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
procession.add_atom(atom)
|
||||
current_atom = procession.get_current_atom()
|
||||
|
||||
assert current_atom == atom
|
||||
|
||||
def test_procession_get_current_atom_empty(self):
|
||||
"""Test getting current atom from empty procession."""
|
||||
procession = Procession()
|
||||
|
||||
current_atom = procession.get_current_atom()
|
||||
|
||||
assert current_atom is None
|
||||
|
||||
def test_procession_move_next(self):
|
||||
"""Test moving to next atom."""
|
||||
procession = Procession()
|
||||
atoms = []
|
||||
for i in range(3):
|
||||
nucleon = Nucleon(content=f"Test{i}", answer=f"Answer{i}")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
atoms.append(atom)
|
||||
procession.add_atom(atom)
|
||||
|
||||
# Start at first atom
|
||||
assert procession.get_current_atom() == atoms[0]
|
||||
assert procession.current_index == 0
|
||||
|
||||
# Move to next
|
||||
procession.move_next()
|
||||
assert procession.get_current_atom() == atoms[1]
|
||||
assert procession.current_index == 1
|
||||
|
||||
# Move to next again
|
||||
procession.move_next()
|
||||
assert procession.get_current_atom() == atoms[2]
|
||||
assert procession.current_index == 2
|
||||
|
||||
# Move beyond end
|
||||
procession.move_next()
|
||||
assert procession.get_current_atom() is None
|
||||
assert procession.current_index == 3
|
||||
|
||||
def test_procession_has_next(self):
|
||||
"""Test checking if there are more atoms."""
|
||||
procession = Procession()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
procession.add_atom(atom)
|
||||
|
||||
# Initially has next (current is first, can move to next)
|
||||
assert procession.has_next() is True
|
||||
|
||||
# Move to next (beyond the only atom)
|
||||
procession.move_next()
|
||||
assert procession.has_next() is False
|
||||
|
||||
def test_procession_is_empty(self):
|
||||
"""Test checking if procession is empty."""
|
||||
procession = Procession()
|
||||
|
||||
assert procession.is_empty() is True
|
||||
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
procession.add_atom(atom)
|
||||
assert procession.is_empty() is False
|
||||
|
||||
def test_procession_clear(self):
|
||||
"""Test clearing procession."""
|
||||
procession = Procession()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
procession.add_atom(atom)
|
||||
procession.clear()
|
||||
|
||||
assert procession.queue == []
|
||||
assert procession.current_index == 0
|
||||
|
||||
def test_procession_validation(self):
|
||||
"""Test input validation for Procession."""
|
||||
procession = Procession()
|
||||
|
||||
# Test add_atom with None
|
||||
with pytest.raises(TypeError):
|
||||
procession.add_atom(None)
|
||||
|
||||
# Test add_atom with invalid type
|
||||
with pytest.raises(TypeError):
|
||||
procession.add_atom("not an atom")
|
||||
|
||||
|
||||
class TestFission:
|
||||
"""Test cases for Fission class."""
|
||||
|
||||
def test_fission_creation(self):
|
||||
"""Test Fission creation."""
|
||||
fission = Fission()
|
||||
|
||||
assert fission.phaser is not None
|
||||
assert isinstance(fission.phaser, Phaser)
|
||||
|
||||
def test_fission_initialize(self):
|
||||
"""Test Fission initialization."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
|
||||
assert fission.phaser.atom == atom
|
||||
assert fission.phaser.current_state == States.LEARNING
|
||||
|
||||
def test_fission_generate_learning_puzzle_cloze(self):
|
||||
"""Test generating learning puzzle with cloze content."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(
|
||||
content="The capital of {{c1::France}} is Paris.",
|
||||
answer="France"
|
||||
)
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
puzzle = fission.generate_learning_puzzle()
|
||||
|
||||
assert puzzle is not None
|
||||
assert "question" in puzzle
|
||||
assert "answer" in puzzle
|
||||
assert "type" in puzzle
|
||||
|
||||
def test_fission_generate_learning_puzzle_mcq(self):
|
||||
"""Test generating learning puzzle with MCQ content."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(
|
||||
content="What is the capital of France?",
|
||||
answer="Paris"
|
||||
)
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
puzzle = fission.generate_learning_puzzle()
|
||||
|
||||
assert puzzle is not None
|
||||
assert "question" in puzzle
|
||||
assert "options" in puzzle
|
||||
assert "correct_index" in puzzle
|
||||
assert "type" in puzzle
|
||||
|
||||
def test_fission_generate_review_puzzle(self):
|
||||
"""Test generating review puzzle."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(
|
||||
content="What is the capital of France?",
|
||||
answer="Paris"
|
||||
)
|
||||
electron = Electron(interval=10, repetitions=5) # In review phase
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
fission.phaser.transition_to_review()
|
||||
puzzle = fission.generate_review_puzzle()
|
||||
|
||||
assert puzzle is not None
|
||||
assert "question" in puzzle
|
||||
|
||||
def test_fission_process_answer_correct(self):
|
||||
"""Test processing correct answer."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
result = fission.process_answer("Answer")
|
||||
|
||||
assert "success" in result
|
||||
assert "quality" in result
|
||||
assert "next_state" in result
|
||||
assert result["success"] is True
|
||||
|
||||
def test_fission_process_answer_incorrect(self):
|
||||
"""Test processing incorrect answer."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
result = fission.process_answer("Wrong")
|
||||
|
||||
assert result["success"] is False
|
||||
|
||||
def test_fission_get_current_state(self):
|
||||
"""Test getting current state."""
|
||||
fission = Fission()
|
||||
nucleon = Nucleon(content="Test", answer="Answer")
|
||||
electron = Electron()
|
||||
orbital = Orbital()
|
||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||
|
||||
fission.initialize(atom)
|
||||
state = fission.get_current_state()
|
||||
|
||||
assert state == States.LEARNING
|
||||
|
||||
def test_fission_validation(self):
|
||||
"""Test input validation for Fission."""
|
||||
fission = Fission()
|
||||
|
||||
# Test initialize with None
|
||||
with pytest.raises(TypeError):
|
||||
fission.initialize(None)
|
||||
|
||||
# Test process_answer without initialization
|
||||
with pytest.raises(RuntimeError):
|
||||
fission.process_answer("test")
|
||||
|
||||
# Test generate_learning_puzzle without initialization
|
||||
with pytest.raises(RuntimeError):
|
||||
fission.generate_learning_puzzle()
|
||||
|
||||
# Test generate_review_puzzle without initialization
|
||||
with pytest.raises(RuntimeError):
|
||||
fission.generate_review_puzzle()
|
||||
Reference in New Issue
Block a user