news 2026/10/16 0:34:05

Python编写单元测试代码实例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python编写单元测试代码实例

前言


unittest是 Python 标准库自带的测试框架,不需要安装任何第三方包。它的设计借鉴了 Java 的 JUnit,所以你会看到TestCase、setUp、tearDown这类命名。虽然现在很多人转向 pytest,但unittest依然值得学:一是零依赖,二是大量存量项目用它,三是理解它的结构后,看别的测试框架会轻松很多。


这篇是实操向:从写第一个TestCase开始,讲到assertEqual系列断言、setUp/tearDown的生命周期、assertRaises怎么验证异常、unittest.mock怎么打桩,最后说清测试目录该怎么组织、怎么用一条命令跑起来。


文中所有 API 名称与参数以 Python 官方文档unittest与unittest.mock页面为准。示例以 Python 3 为基准。


一、第一个 TestCase


核心结构是:继承unittest.TestCase,把每个测试写成一个以test_开头的方法。


# 适用于 Python 3.8+
import unittest

def add(a, b):
return a + b

def divide(a, b):
if b == 0:
raise ValueError("除数不能为 0")
return a / b

class TestCalculator(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)

def test_divide(self):
self.assertEqual(divide(10, 2), 5)

def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(1, 0)

if __name__ == "__main__":
unittest.main()

几点解释:



  • 方法名必须以test开头,否则测试加载器不会收集它。这是最常见的「写了测试却一个都没跑」的原因。

  • unittest.main()会自动发现本模块里的测试并运行,方便单文件直接python xxx.py执行。

  • 断言失败时,unittest会打印期望值与实际值,并指出是哪个方法、哪一行。


二、常用断言方法


TestCase提供了一整套以assert开头的方法。不要用裸assert语句,因为assert在-O优化模式下会被整体移除,测试就形同虚设。用断言方法还能得到更好的失败信息。




方法用途备注



assertEqual(a, b)判断相等最常用,底层用==

assertNotEqual(a, b)判断不等

assertTrue(x)/assertFalse(x)判断真假

assertIs(a, b)/assertIsNot(a, b)判断同一对象比assertEqual严格

assertIsNone(x)判断是None

assertIn(a, b)/assertNotIn(a, b)成员判断

assertRaises(exc)断言抛异常上下文管理器用法

assertAlmostEqual(a, b)浮点近似相等避免浮点直接比较

assertCountEqual(a, b)两序列元素相同(不计顺序)



一个补充说明:旧的assertEquals、assertNotEquals是assertEqual、assertNotEqual的别名,官方已把它们标记为废弃(deprecated),新代码应当用不带s的版本。


浮点数比较要特别注意,0.1 + 0.2 == 0.3在二进制浮点下不成立,所以应该用assertAlmostEqual:


# 适用于 Python 3.8+
import unittest

class TestFloat(unittest.TestCase):
def test_float_sum(self):
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)

def test_raises_with_message(self):
with self.assertRaises(KeyError) as ctx:
{}["missing"]
self.assertIn("missing", str(ctx.exception))

三、setUp 与 tearDown:测试夹具


测试之间应当相互独立,一个测试的状态不能泄漏到下一个。setUp()在每个测试方法之前运行,tearDown()在每个测试方法之后运行——用它们准备和清理夹具(fixture)。


# 适用于 Python 3.8+
import unittest

class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
result = a + b
self.history.append(result)
return result

class TestCalculator(unittest.TestCase):
def setUp(self):
# 每个测试都拿到一个全新的实例,互不污染
self.calc = Calculator()

def tearDown(self):
# 每个测试结束后清理(这里没有外部资源,仅演示位置)
self.calc = None

def test_add_records_history(self):
self.calc.add(1, 2)
self.assertEqual(self.calc.history, [3])

def test_history_starts_empty(self):
# 上一个测试加过的结果不会出现在这里
self.assertEqual(self.calc.history, [])

生命周期规则要记准:



  • setUp()抛异常,该测试方法不会执行,tearDown()也不会执行;

  • 如果setUp()成功,则无论测试方法是否失败,tearDown()都会执行;

  • 更粗粒度还有setUpClass()/tearDownClass()(用@classmethod装饰,整个类跑一次)和setUpModule()/tearDownModule()(整个模块跑一次),适合建立昂贵的、所有测试共享的资源,比如临时数据库。


四、用 mock 隔离外部依赖


单元测试只应测「被测单元」,不该真的去连数据库、发 HTTP 请求。unittest.mock用来把这些外部依赖替换成可控的假对象。最常用的是patch,它既可以当装饰器,也可以当上下文管理器。


# 适用于 Python 3.8+
# 被测模块 weather.py 里写:
# import requests
# def get_temp(city):
# resp = requests.get(f"https://api.example.com/{city}")
# return resp.json()["temp"]

import unittest
from unittest.mock import patch, MagicMock
import weather # 上面那个被测模块

class TestGetTemp(unittest.TestCase):
@patch("weather.requests") # patch 被测模块里的名字,不是全局 requests
def test_get_temp(self, mock_requests):
mock_requests.get.return_value.json.return_value = {"temp": 21}
self.assertEqual(weather.get_temp("shanghai"), 21)
mock_requests.get.assert_called_once_with("https://api.example.com/shanghai")

def test_with_magicmock_argument(self):
# 另一种做法:把假的 client 当参数注入,不碰请求库
client = MagicMock()
client.get.return_value.json.return_value = {"name": "Ada"}
resp = client.get("/users/1")
self.assertEqual(resp.json()["name"], "Ada")

关键要点:



  • patch 的位置要 patch「使用它的地方」,而不是「定义它的地方」。上面被测模块weather.py里import requests后调用requests.get,就应该 patchweather.requests这个名字,而不是全局的requests。

  • MagicMock会自动生成属性和方法,所以client.get().json()这种链式调用不需要手工打桩。

  • assert_called_once_with(...)、assert_called_with(...)用来验证调用参数,是「行为测试」的常用手段。

  • 想模拟异常,给 mock 设side_effect。


五、测试目录怎么组织,怎么跑起来


推荐把源码和测试分开放,测试文件名以test_开头,这样unittest的自动发现能识别:


project/
├── myservice/
│ ├── __init__.py
│ └── calculator.py
└── tests/
├── __init__.py
├── test_calculator.py
└── test_api.py

运行方式有三种:


# 1. 跑单个文件
python -m unittest tests.test_calculator

# 2. 自动发现当前目录下所有 test*.py
python -m unittest discover

# 3. 指定起始目录与匹配模式
python -m unittest discover -s tests -p "test_*.py" -v

注意discover默认匹配的是test*.py(test开头),不是*_test.py。如果你的文件命名成calculator_test.py,默认发现不了——要么改文件名,要么用-p指定模式。另外,测试目录里放一个__init__.py能让「按包名运行」在多数配置下更稳妥。


常见坑点



  1. 测试方法不以test开头


❌def check_add(self): ...


✅def test_add(self): ...



  1. 用裸 assert 而不是断言方法


❌assert add(1, 1) == 2(-O下会被优化掉)


✅self.assertEqual(add(1, 1), 2)



  1. 测试之间共享可变状态


❌ 在类属性里建一个列表,多个测试往里塞数据


✅ 在setUp()里每个测试新建独立实例



  1. 浮点数直接assertEqual


❌self.assertEqual(0.1 + 0.2, 0.3)(不相等)


✅self.assertAlmostEqual(0.1 + 0.2, 0.3)



  1. patch 错了目标


❌ patch 库定义处,结果被测模块用了自己的引用,没被替换


✅ patch 被测模块里「引用它的那个名字」



  1. 测试里做真实的网络 / 数据库访问


❌ 测试依赖外部服务是否在线,慢且不稳


✅ 用mock隔离外部依赖



  1. 文件名不匹配发现规则


❌calculator_test.py用默认discover跑不到


✅ 改成test_calculator.py,或用-p指定模式



  1. 依赖tearDown在setUp失败时执行


❌setUp抛异常还想靠tearDown清理


✅ 记住setUp失败时tearDown不会执行,清理逻辑要能容忍


总结




概念作用关键约束



TestCase测试容器方法名以test开头

setUp/tearDown每个测试前后setUp失败则tearDown不跑

setUpClass/tearDownClass每类一次需@classmethod

断言方法验证结果别用裸assert

mock.patch隔离依赖patch 使用处而非定义处

discover批量运行默认匹配test*.py



unittest的骨架说穿了就四块:继承TestCase、test_命名、setUp准备、断言验证。把这四块用熟练,再学 pytest 只是换个更顺手的皮。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 0:10:25

真实列车数据工程实战:从PDF解析到交互式热力图

简介:这是一份面向计算机及相关专业本科生的Python数据分析与可视化实战项目源码,聚焦全国列车数据的采集、清洗、分析与多维可视化呈现,适用于期末大作业、课程设计及项目能力提升场景。资源包共32个文件,包含6个核心Python脚本&…

作者头像 李华
网站建设 2026/10/14 19:17:42

正余弦优化算法(SCA)改进:引入莱维飞行机制的原理与Python复现实战

复现一篇改进正余弦优化算法(SCA)的论文,是我最近两周的主要任务。论文里的SCASL,简单说就是在标准SCA基础上引入莱维飞行机制,来缓解算法早熟收敛的问题。整个过程走下来,最大的感受是:这类改进…

作者头像 李华
网站建设 2026/10/15 23:57:08

Hadoop+SSH框架实现HDFS网盘:从环境搭建到Java API实战

简介:这份资源面向大数据与分布式系统方向的开发者及高校学生,提供一套基于Hadoop平台、借助SSH框架实现HDFS网盘的完整项目源码,帮助读者理解分布式存储与远程安全访问的落地方式。压缩包共629个文件,约37.4MB,涵盖60…

作者头像 李华
网站建设 2026/10/15 13:59:49

Java+SpringBoot+SSM智能医疗辅助系统实战解析

智能医疗辅助系统这个项目,我从去年开始陆续带过好几个版本的开发,从纯SSM架构到SpringBoot整合,从单机部署到前后端分离,踩过的坑加起来能写一本小册子。回头看,“基于JavaSpringBootSSM的智能医疗辅助系统”这个选题…

作者头像 李华