# Normalizer Tests

每个 normalizer 模块都有完整的测试覆盖。

## 测试结构

```
tests/normalize/normalizers/
├── __init__.py
├── test_invoice_number_normalizer.py    # InvoiceNumberNormalizer 测试 (12 个测试)
├── test_ocr_normalizer.py               # OCRNormalizer 测试 (9 个测试)
├── test_bankgiro_normalizer.py          # BankgiroNormalizer 测试 (11 个测试)
├── test_plusgiro_normalizer.py          # PlusgiroNormalizer 测试 (10 个测试)
├── test_amount_normalizer.py            # AmountNormalizer 测试 (15 个测试)
├── test_date_normalizer.py              # DateNormalizer 测试 (19 个测试)
├── test_organisation_number_normalizer.py  # OrganisationNumberNormalizer 测试 (11 个测试)
├── test_supplier_accounts_normalizer.py    # SupplierAccountsNormalizer 测试 (13 个测试)
├── test_customer_number_normalizer.py      # CustomerNumberNormalizer 测试 (12 个测试)
└── README.md                            # 本文件
```

## 运行测试

### 运行所有 normalizer 测试

```bash
# 在 WSL 环境中
conda activate invoice-py311
pytest tests/normalize/normalizers/ -v
```

### 运行单个 normalizer 的测试

```bash
# 测试 InvoiceNumberNormalizer
pytest tests/normalize/normalizers/test_invoice_number_normalizer.py -v

# 测试 AmountNormalizer
pytest tests/normalize/normalizers/test_amount_normalizer.py -v

# 测试 DateNormalizer
pytest tests/normalize/normalizers/test_date_normalizer.py -v
```

### 查看测试覆盖率

```bash
pytest tests/normalize/normalizers/ --cov=src/normalize/normalizers --cov-report=html
```

## 测试统计

**总计**: 112 个测试用例
**状态**: ✅ 全部通过
**执行时间**: ~5.6 秒

### 各 Normalizer 测试数量

| Normalizer | 测试数量 | 覆盖率 |
|------------|---------|-------|
| InvoiceNumberNormalizer | 12 | 100% |
| OCRNormalizer | 9 | 100% |
| BankgiroNormalizer | 11 | 100% |
| PlusgiroNormalizer | 10 | 100% |
| AmountNormalizer | 15 | 100% |
| DateNormalizer | 19 | 93% |
| OrganisationNumberNormalizer | 11 | 100% |
| SupplierAccountsNormalizer | 13 | 100% |
| CustomerNumberNormalizer | 12 | 100% |

## 测试覆盖的场景

### 通用测试 (所有 normalizer)

- ✅ 空字符串处理
- ✅ None 值处理
- ✅ Callable 接口 (`__call__`)
- ✅ 基本功能验证

### InvoiceNumberNormalizer

- ✅ 纯数字发票号
- ✅ 带前缀的发票号 (INV-, etc.)
- ✅ 字母数字混合
- ✅ 特殊字符处理
- ✅ Unicode 字符清理
- ✅ 多个分隔符
- ✅ 无数字内容
- ✅ 重复变体去除

### OCRNormalizer

- ✅ 纯数字 OCR
- ✅ 带前缀 (OCR:)
- ✅ 空格分隔
- ✅ 连字符分隔
- ✅ 混合分隔符
- ✅ 超长 OCR 号码

### BankgiroNormalizer

- ✅ 8 位数字 (带/不带连字符)
- ✅ 7 位数字格式
- ✅ 特殊连字符类型 (en-dash, etc.)
- ✅ 空格处理
- ✅ 前缀处理 (BG:)
- ✅ OCR 错误变体生成

### PlusgiroNormalizer

- ✅ 8 位数字 (带/不带连字符)
- ✅ 7 位数字
- ✅ 9 位数字
- ✅ 空格处理
- ✅ 前缀处理 (PG:)
- ✅ OCR 错误变体生成

### AmountNormalizer

- ✅ 整数金额
- ✅ 逗号小数分隔符
- ✅ 点小数分隔符
- ✅ 空格千位分隔符
- ✅ 空格作为小数分隔符 (瑞典格式)
- ✅ 美国格式 (1,390.00)
- ✅ 欧洲格式 (1.390,00)
- ✅ 货币符号移除 (kr, SEK)
- ✅ 大金额处理
- ✅ 冒号破折号后缀 (1234:-)

### DateNormalizer

- ✅ ISO 格式 (2025-12-13)
- ✅ 欧洲斜杠格式 (13/12/2025)
- ✅ 欧洲点格式 (13.12.2025)
- ✅ 紧凑格式 YYYYMMDD
- ✅ 紧凑格式 YYMMDD
- ✅ 短年份格式 (DD.MM.YY)
- ✅ 瑞典月份名称 (december, dec)
- ✅ 瑞典月份缩写
- ✅ 带时间的 ISO 格式
- ✅ 歧义日期双重解析
- ✅ 中点分隔符
- ✅ 空格格式
- ✅ 无效日期处理
- ✅ 2 位年份世纪判断

### OrganisationNumberNormalizer

- ✅ 带/不带连字符
- ✅ VAT 号码提取
- ✅ VAT 号码生成
- ✅ 12 位带世纪组织号
- ✅ VAT 带空格
- ✅ 大小写混合 VAT 前缀
- ✅ OCR 错误变体生成

### SupplierAccountsNormalizer

- ✅ 单个 Plusgiro
- ✅ 单个 Bankgiro
- ✅ 多账号 (| 分隔)
- ✅ 前缀标准化
- ✅ 前缀带空格
- ✅ 空账号忽略
- ✅ 无前缀账号
- ✅ 7 位账号
- ✅ 10 位账号
- ✅ 混合格式账号

### CustomerNumberNormalizer

- ✅ 字母数字+空格+连字符
- ✅ 字母数字+空格
- ✅ 大小写变体
- ✅ 纯数字
- ✅ 仅连字符
- ✅ 仅空格
- ✅ 大写重复去除
- ✅ 复杂客户编号
- ✅ 瑞典客户编号格式 (UMJ 436-R)

## 最佳实践

### 1. 使用 pytest fixtures

每个测试类都使用 `@pytest.fixture` 创建 normalizer 实例:

```python
@pytest.fixture
def normalizer(self):
    """Create normalizer instance for testing"""
    return InvoiceNumberNormalizer()

def test_something(self, normalizer):
    result = normalizer.normalize('test')
    assert 'expected' in result
```

### 2. 清晰的测试命名

测试方法名清楚描述测试场景:

```python
def test_with_dash_8_digits(self, normalizer):
    """8-digit Bankgiro with dash should generate variants"""
    ...
```

### 3. 断言具体行为

明确测试期望的行为:

```python
result = normalizer.normalize('5393-9484')
assert '5393-9484' in result  # 保留原始格式
assert '53939484' in result   # 生成无连字符格式
```

### 4. 边界条件测试

每个 normalizer 都测试:
- 空字符串
- None 值
- 特殊字符
- 极端值

### 5. 接口一致性测试

验证 callable 接口:

```python
def test_callable_interface(self, normalizer):
    """Normalizer should be callable via __call__"""
    result = normalizer('test-value')
    assert result is not None
```

## 添加新测试

为新功能添加测试:

```python
def test_new_feature(self, normalizer):
    """Description of what this tests"""
    # Arrange
    input_value = 'test-input'

    # Act
    result = normalizer.normalize(input_value)

    # Assert
    assert 'expected-output' in result
    assert len(result) > 0
```

## CI/CD 集成

这些测试可以轻松集成到 CI/CD 流程:

```yaml
# .github/workflows/test.yml
- name: Run Normalizer Tests
  run: pytest tests/normalize/normalizers/ -v --cov
```

## 总结

✅ **112 个测试**全部通过
✅ **高覆盖率**: 大部分 normalizer 达到 100%
✅ **快速执行**: 5.6 秒完成所有测试
✅ **清晰结构**: 每个 normalizer 独立测试文件
✅ **易维护**: 遵循 pytest 最佳实践
