学习目标 ✅ 掌握Python测试框架(对比Java的JUnit/Mockito) ✅ 实现单元测试、集成测试与端到端测试 ✅ 集成测试覆盖率与持续化工具 ✅ 完成博客系统全链路测试实战
功能 | Java | Python | 核心差异 |
---|---|---|---|
单元测试框架 | JUnit 5 | unittest/pytest | pytest语法更简洁 |
Mock框架 | Mockito | unittest.mock/pytest-mock | Python的Mock更灵活 |
测试覆盖率 | JaCoCo | coverage | 使用方式类似 |
端到端测试 | Selenium | selenium + pytest | 接口一致 |
持续集成 | Jenkins + Maven插件 | GitHub Actions + pytest | 配置更简单 |
# test_calculator.py(类似Java的Test Class)
import unittest
def add(a, b):
return a + b
class TestCalculator(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5) # 类似JUnit的assertEquals
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
if __name__ == "__main__":
unittest.main()
from unittest.mock import Mock
# 模拟数据库查询(类似Mockito.when)
def test_user_query(mocker):
mock_db = Mock()
mock_db.execute.return_value = [("Alice", 30)]
user_service = UserService(mock_db)
result = user_service.get_user(1)
assert result.name == "Alice"
mock_db.execute.assert_called_once_with("SELECT * FROM users WHERE id=1")
import pytest
from app import create_app, db
@pytest.fixture
def client():
app = create_app(testing=True)
with app.test_client() as client:
with app.app_context():
db.create_all()
yield client
with app.app_context():
db.drop_all()
def test_create_post(client):
response = client.post("/posts", json={"title": "Test", "content": "Hello"})
assert response.status_code == 201
assert "id" in response.json
requests
测试REST APIdef test_api_posts():
response = requests.get("http://localhost:5000/api/posts")
assert response.status_code == 200
assert isinstance(response.json(), list)
# 安装工具
pip install coverage
# 运行测试并生成报告
coverage run -m pytest tests/
coverage html # 生成HTML报告
报告示例:
复制
Name Stmts Miss Cover
-------------------------------------
app/__init__.py 12 0 100%
app/models.py 20 1 95%
# 使用pylint进行静态分析
pip install pylint
pylint app/
# 使用bandit检查安全漏洞
pip install bandit
bandit -r app/
模型层测试(使用SQLite内存数据库):
def test_post_model(session):
post = Post(title="Test", content="Content")
session.add(post)
session.commit()
assert post.id is not None
assert Post.query.count() == 1
API测试(JSON Schema验证):
from jsonschema import validate
post_schema = {
"type": "object",
"properties": {
"id": {"type": "number"},
"title": {"type": "string"}
},
"required": ["id", "title"]
}
def test_get_post(client):
response = client.get("/api/posts/1")
validate(response.json(), post_schema)
端到端测试(Selenium):
from selenium.webdriver import Chrome
def test_user_flow():
driver = Chrome()
driver.get("http://localhost:5000/register")
driver.find_element("name", "username").send_keys("testuser")
driver.find_element("name", "password").send_keys("secret123")
driver.find_element("tag name", "button").click()
assert "Welcome" in driver.page_source
driver.quit()
# .github/workflows/test.yml
name: Python CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- run: pip install -r requirements.txt
- run: pytest --cov=app tests/
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Notify DingTalk
uses: nicolasnoble/dingtalk-action@v1
with:
access_token: ${{ secrets.DINGTALK_TOKEN }}
msgtype: markdown
content: |
## 测试结果
- 覆盖率: ${{ coverage }}%
- 通过率: ${{ success }}
测试固件差异
setUp
/tearDown
(类似JUnit 4)
pytest
的fixture
更灵活(类似JUnit 5扩展模型)
异步测试处理
@pytest.mark.asyncio
async def test_async_api():
async with AsyncClient() as client:
response = await client.get("/async")
assert response.status_code == 200
测试环境隔离
pytest
的monkeypatch
修改环境变量
def test_config(monkeypatch):
monkeypatch.setenv("DB_URI", "sqlite:///:memory:")
assert get_config().db_uri == "sqlite:///:memory:"
参数化测试(类似JUnit的@ParameterizedTest)
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(-1, 1, 0)
])
def test_add(a, b, expected):
assert add(a, b) == expected
测试驱动开发(TDD)实战
性能基准测试
def test_api_performance(benchmark):
result = benchmark(requests.get, "http://api/posts")
assert result.status_code == 200
通过第十四天学习,您将掌握: 1️⃣ Python测试生态的核心工具链 2️⃣ 分层测试策略设计与实施 3️⃣ 生产级持续集成流水线搭建 4️⃣ 从单元测试到端到端测试的完整解决方案
本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注。