Add tests, server, resume training, and project cleanup
- Add 57 unit tests covering generators, models, and pipeline components - Implement FastAPI HTTP service (server.py) with POST /solve and GET /health - Add checkpoint resume (断点续训) to both CTC and regression training utils - Fix device mismatch bug in CTC training (targets/input_lengths on GPU) - Add pytest dev dependency to pyproject.toml - Update .gitignore with data/solver/, data/real/, *.log - Remove PyCharm template main.py - Update training/__init__.py docs for solver training scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
173
tests/test_generators.py
Normal file
173
tests/test_generators.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
测试所有验证码生成器。
|
||||
|
||||
每种生成器 generate() 1 张 → 验证返回类型、图片尺寸、标签格式。
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from config import GENERATE_CONFIG, NORMAL_CHARS, MATH_CHARS, THREED_CHARS, SOLVER_CONFIG
|
||||
from generators import (
|
||||
NormalCaptchaGenerator,
|
||||
MathCaptchaGenerator,
|
||||
ThreeDCaptchaGenerator,
|
||||
ThreeDRotateGenerator,
|
||||
ThreeDSliderGenerator,
|
||||
SlideDataGenerator,
|
||||
RotateSolverDataGenerator,
|
||||
)
|
||||
|
||||
|
||||
class TestNormalCaptchaGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = NormalCaptchaGenerator(seed=0)
|
||||
self.cfg = GENERATE_CONFIG["normal"]
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
w, h = self.cfg["image_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_chars_in_charset(self):
|
||||
img, label = self.gen.generate()
|
||||
assert len(label) >= 4
|
||||
for ch in label:
|
||||
assert ch in NORMAL_CHARS, f"char {ch!r} not in NORMAL_CHARS"
|
||||
|
||||
def test_generate_with_text(self):
|
||||
img, label = self.gen.generate(text="AB12")
|
||||
assert label == "AB12"
|
||||
|
||||
|
||||
class TestMathCaptchaGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = MathCaptchaGenerator(seed=0)
|
||||
self.cfg = GENERATE_CONFIG["math"]
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
w, h = self.cfg["image_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_is_expression(self):
|
||||
"""Label should be like '3+8' (expression without =? and without result)."""
|
||||
img, label = self.gen.generate()
|
||||
assert re.match(r"^\d+[+\-×÷]\d+$", label), f"unexpected label format: {label!r}"
|
||||
|
||||
|
||||
class TestThreeDCaptchaGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = ThreeDCaptchaGenerator(seed=0)
|
||||
self.cfg = GENERATE_CONFIG["3d_text"]
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
w, h = self.cfg["image_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_chars_in_charset(self):
|
||||
img, label = self.gen.generate()
|
||||
assert len(label) >= 4
|
||||
for ch in label:
|
||||
assert ch in THREED_CHARS, f"char {ch!r} not in THREED_CHARS"
|
||||
|
||||
|
||||
class TestThreeDRotateGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = ThreeDRotateGenerator(seed=0)
|
||||
self.cfg = GENERATE_CONFIG["3d_rotate"]
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
w, h = self.cfg["image_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_is_angle(self):
|
||||
img, label = self.gen.generate()
|
||||
angle = int(label)
|
||||
assert 0 <= angle <= 359
|
||||
|
||||
|
||||
class TestThreeDSliderGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = ThreeDSliderGenerator(seed=0)
|
||||
self.cfg = GENERATE_CONFIG["3d_slider"]
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
w, h = self.cfg["image_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_is_offset(self):
|
||||
img, label = self.gen.generate()
|
||||
offset = int(label)
|
||||
lo, hi = self.cfg["gap_x_range"]
|
||||
assert lo <= offset <= hi
|
||||
|
||||
|
||||
class TestSlideDataGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = SlideDataGenerator(seed=0)
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
h, w = SOLVER_CONFIG["slide"]["cnn_input_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_is_numeric(self):
|
||||
img, label = self.gen.generate()
|
||||
val = int(label)
|
||||
assert val >= 0
|
||||
|
||||
|
||||
class TestRotateSolverDataGenerator:
|
||||
def setup_method(self):
|
||||
self.gen = RotateSolverDataGenerator(seed=0)
|
||||
|
||||
def test_generate_returns_image_and_label(self):
|
||||
img, label = self.gen.generate()
|
||||
assert isinstance(img, Image.Image)
|
||||
assert isinstance(label, str)
|
||||
|
||||
def test_image_size(self):
|
||||
img, _ = self.gen.generate()
|
||||
h, w = SOLVER_CONFIG["rotate"]["input_size"]
|
||||
assert img.size == (w, h)
|
||||
|
||||
def test_label_is_angle(self):
|
||||
img, label = self.gen.generate()
|
||||
angle = int(label)
|
||||
assert 0 <= angle <= 359
|
||||
Reference in New Issue
Block a user