别再只盯着try-except了!用pytest给你的JSON解析函数做个全面体检(附常见坑点)
JSON作为现代应用中最常用的数据交换格式之一,几乎每个Python开发者都写过JSON解析代码。但你是否发现,当你的函数面对空字符串、HTML片段或编码错误的输入时,那些精心编写的try-except块可能根本来不及发挥作用?本文将带你用pytest构建一个"ICU级"的JSON解析测试套件,让你的代码在投产前就具备抗摔打能力。
1. 为什么单元测试比异常处理更重要
异常处理就像急救箱,而单元测试则是体检中心。当你的JSON解析函数在生产环境崩溃时,try-except确实能防止程序完全中断,但用户已经看到了错误页面。通过预先设计的测试用例,我们能在开发阶段就发现潜在问题。
考虑这个典型场景:
def parse_json(data): try: return json.loads(data) except json.JSONDecodeError: return None表面看很安全,但以下输入会暴露问题:
- 包含HTML标签的字符串(如
<div>伪JSON</div>) - 字节序列与非UTF-8编码混合的数据
- 格式正确但包含特殊控制字符的JSON
提示:好的测试应该像黑客攻击一样思考——不仅要测"应该怎么用",更要测"可能被怎么滥用"
2. 构建你的JSON测试武器库
2.1 基础测试框架搭建
首先安装必要的测试依赖:
pip install pytest pytest-cov创建测试文件结构:
tests/ ├── __init__.py └── test_json_parser.py基础测试示例:
import pytest from your_module import parse_json def test_valid_json(): assert parse_json('{"key": "value"}') == {"key": "value"}2.2 参数化测试的艺术
pytest.mark.parametrize让批量测试变得优雅:
@pytest.mark.parametrize("invalid_input", [ "", # 空字符串 None, # None值 "<html>fake</html>",# 非JSON文本 b'\xc3\x28', # 非法UTF-8 "{'single_quotes': true}", # JSON标准要求双引号 '{"trailing": comma,}', # 非法逗号 ]) def test_invalid_json(invalid_input): assert parse_json(invalid_input) is None2.3 边缘案例特别护理
有些错误需要定制化检测:
def test_partial_json(): # 测试解析器对不完整JSON的处理 with pytest.raises(json.JSONDecodeError): parse_json('{"incomplete":') def test_deep_nesting(): # 测试栈溢出防护 deep_json = '{"a":' * 1000 + 'null' + '}' * 1000 assert parse_json(deep_json) is not None3. 高级测试技巧实战
3.1 模拟网络异常测试
使用pytest-mock模拟请求异常:
def test_network_json(mocker): mock_response = mocker.Mock() mock_response.json.side_effect = json.JSONDecodeError("Expecting value", "", 0) mocker.patch('requests.get', return_value=mock_response) result = fetch_json_from_api("http://example.com") assert result == {}3.2 性能与安全测试
JSON解析可能成为性能瓶颈或攻击载体:
class TestPerformance: @pytest.mark.timeout(1) def test_large_json(self): large_data = {"items": [{"id": i} for i in range(100000)]} assert parse_json(json.dumps(large_data))3.3 自定义断言增强可读性
创建更有表现力的断言:
def assert_valid_json(result): assert isinstance(result, (dict, list)) assert not isinstance(result, str) def test_custom_assertion(): result = parse_json('["valid"]') assert_valid_json(result)4. 常见坑点与解决方案
| 坑点类型 | 典型表现 | 解决方案 |
|---|---|---|
| 编码问题 | UnicodeDecodeError先于JSONDecodeError | 先检测编码,再解析 |
| 内存爆炸 | 1MB文件占用1GB内存 | 使用ijson流式解析 |
| 日期处理 | datetime序列化失败 | 自定义JSONEncoder |
| 浮点精度 | 1.0000000000000001变成1.0 | 使用decimal.Decimal |
5. 构建持续测试流水线
将测试集成到CI流程中:
# .github/workflows/test.yml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: pip install -r requirements.txt - run: pytest --cov=your_module tests/ - run: pytest --benchmark-only test_performance.py最后分享一个真实案例:某电商平台曾因JSON解析器未处理Infinity数值导致支付系统崩溃。他们在测试套件中增加了以下用例后问题再未重现:
@pytest.mark.parametrize("special_float", [ "Infinity", "-Infinity", "NaN" ]) def test_nonstandard_floats(special_float): with pytest.raises(ValueError): parse_json(f'{{"value": {special_float}}}')