news 2026/4/26 3:34:46

Day 34:【99天精通Python】单元测试 (Unittest) - 给代码上个保险

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 34:【99天精通Python】单元测试 (Unittest) - 给代码上个保险

Day 34:【99天精通Python】单元测试 (Unittest) - 给代码上个保险

前言

欢迎来到第34天!

在之前的开发中,我们通常是怎么验证代码对不对的?
—— 写完代码,手动运行一下,输入几个参数,看看打印结果对不对。
这种人工测试方式在小脚本里还行,但当项目变大时:

  1. 效率低:每次改个小功能,都要把所有功能手动测一遍,累死人。
  2. 不可靠:人是会犯错的,容易漏测某些边界情况(比如除以0、空输入)。
  3. 不敢重构:想优化代码,又怕改坏了以前的功能,导致不敢动。

单元测试 (Unit Testing)就是为了解决这些问题。我们写一段代码(测试脚本)来专门检测另一段代码(业务逻辑)。一旦业务代码被修改,只需一键运行测试脚本,几秒钟就能知道有没有把旧功能改坏。

本节内容:

  • 什么是单元测试?
  • Python 内置模块unittest
  • 常用断言方法 (assertEqual,assertTrue…)
  • 测试固件:setUptearDown
  • 实战练习:为银行账户类编写测试

一、第一个测试用例

假设我们有一个简单的函数add(a, b)

# math_func.pydefadd(a,b):returna+b

我们想测试它对不对。使用unittest模块:

# test_math.pyimportunittestfrommath_funcimportadd# 1. 定义一个测试类,必须继承 unittest.TestCaseclassTestMathFunc(unittest.TestCase):# 2. 定义测试方法,必须以 test_ 开头deftest_add_integers(self):"""测试整数相加"""result=add(1,2)# 3. 断言:判断 result 是否等于 3self.assertEqual(result,3)deftest_add_strings(self):"""测试字符串相加"""result=add("hello"," world")self.assertEqual(result,"hello world")# 4. 运行测试if__name__=='__main__':unittest.main()

运行test_math.py,如果一切正常,控制台会显示OK。如果把add函数改成return a - b,运行测试就会报错FAIL


二、常用断言 (Assertions)

断言是测试的核心,用来判断"实际结果"与"预期结果"是否一致。

方法检查内容
assertEqual(a, b)a == b
assertNotEqual(a, b)a != b
assertTrue(x)x为真
assertFalse(x)x为假
assertIn(item, container)item在容器中
assertIsNone(x)x是 None
assertRaises(Error, func)func执行时抛出指定异常

示例:测试除法异常

defdivide(a,b):ifb==0:raiseValueError("除数不能为0")returna/bclassTestDivide(unittest.TestCase):deftest_divide_zero(self):# 断言:执行 divide(10, 0) 时应该抛出 ValueErrorwithself.assertRaises(ValueError):divide(10,0)

三、测试固件:setUp 与 tearDown

有时候,每个测试用例运行前都需要做一些准备工作(比如连接数据库、创建临时文件),运行后需要清理(断开连接、删除文件)。
这时就用到setUp(前置)和tearDown(后置)。

  • setUp(): 每个test_方法运行自动执行。
  • tearDown(): 每个test_方法运行自动执行。
classTestDatabase(unittest.TestCase):defsetUp(self):print("\n--- 连接数据库 ---")self.db={"user":"admin"}# 模拟数据库连接deftearDown(self):print("--- 断开连接 ---")self.db=Nonedeftest_query(self):print("执行查询测试")self.assertEqual(self.db["user"],"admin")deftest_update(self):print("执行更新测试")self.db["user"]="root"self.assertEqual(self.db["user"],"root")

执行顺序

  1. setUp->test_query->tearDown
  2. setUp->test_update->tearDown

注意:每个测试用例之间是相互独立的。test_update修改了数据,不会影响test_query(因为它在下一次运行时会重新setUp)。


四、实战练习:测试银行账户类

回顾一下 Day 14 写的BankAccount类,我们给它加上单元测试。

业务代码 (bank.py)

classBankAccount:def__init__(self,balance=0):self.balance=balancedefdeposit(self,amount):ifamount<=0:raiseValueError("存款金额必须大于0")self.balance+=amountreturnself.balancedefwithdraw(self,amount):ifamount>self.balance:raiseValueError("余额不足")self.balance-=amountreturnself.balance

测试代码 (test_bank.py)

importunittestfrombankimportBankAccountclassTestBankAccount(unittest.TestCase):defsetUp(self):# 每次测试前,都创建一个初始余额为 100 的账户self.account=BankAccount(100)deftest_deposit(self):"""测试存款正常"""new_balance=self.account.deposit(50)self.assertEqual(new_balance,150)self.assertEqual(self.account.balance,150)deftest_deposit_negative(self):"""测试存负数报错"""withself.assertRaises(ValueError):self.account.deposit(-10)deftest_withdraw(self):"""测试取款正常"""self.account.withdraw(30)self.assertEqual(self.account.balance,70)deftest_withdraw_overdraft(self):"""测试透支报错"""withself.assertRaises(ValueError):self.account.withdraw(200)if__name__=='__main__':unittest.main()

五、如何运行测试

5.1 命令行运行

在终端中,不需要修改代码,直接运行:

# 运行指定文件python -m unittest test_bank.py# 自动发现并运行当前目录下所有 test_*.py 文件python -m unittest discover

5.2 生成测试报告

虽然unittest自带的输出很简单,但我们可以配合第三方库(如HTMLTestRunner,不过现在更流行用pytest)来生成漂亮的 HTML 报告。我们会在进阶课程的最后介绍更强大的pytest框架。现在先掌握unittest的基础。


六、常见问题

Q1:为什么我的测试方法没运行?

请检查方法名是否以test_开头。如果写成def check_add(self):unittest会忽略它。

Q2:测试代码和业务代码应该放在一起吗?

不建议。通常在项目根目录下创建一个tests/文件夹,专门存放测试脚本。

Q3:unittestpytest哪个好?

  • unittest:Python 内置,不需要安装,符合 xUnit 标准(类、setUp/tearDown),适合学习原理。
  • pytest:第三方库,语法更简单(不需要写类,直接写 assert),插件丰富。实际工作中推荐用 pytest,但它是建立在对测试原理理解的基础上的。

七、小结

单元测试 unittest

TestCase 类

断言方法

生命周期

继承 unittest.TestCase

方法名必须以 test_ 开头

assertEqual (相等)

assertTrue (真)

assertRaises (异常)

setUp() (每例开始前)

test_xxx() (运行测试)

tearDown() (每例结束后)

关键要点

  1. 测试是保险:它保证了你的代码在修改后依然符合预期。
  2. 独立性:每个测试用例应该是独立的,互不干扰。
  3. 覆盖率:不仅要测"正常情况",更要测"异常情况"(如存负数、取款透支)。

八、课后作业

  1. 字符串工具测试:编写一个函数is_palindrome(s)判断字符串是否为回文(如 “aba”)。编写测试用例,覆盖:回文串、非回文串、空字符串、含空格的字符串。
  2. 购物车测试:为 Day 16 的Cart类编写测试。测试功能:add添加商品,len计算数量,以及确保添加重复商品时逻辑是否符合预期(假设允许重复)。
  3. 修复 Bug:在练习1中,如果输入的字符串包含大小写(如 “Racecar”),你的函数能正确判断吗?编写一个失败的测试用例,然后修改原函数代码,直到测试通过。

下节预告

Day 35:综合实战 - 爬虫与数据分析可视化(上)- 进阶篇的理论知识学得差不多了。接下来的几天,我们将做一个真·实战项目:爬取真实的网站数据,存入数据库,并生成精美的图表!


系列导航

  • 上一篇:Day 33 - 日志记录Logging
  • 下一篇:Day 35 - 综合实战爬虫与可视化上(待更新)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 21:45:11

【系统架构师备考笔记】006 电子政务角色关系

一、核心角色定位政府定义&#xff1a;主导者与服务提供者职责政策制定与平台运营在线服务交付&#xff08;如税务申报 $T\int_{a}^{b} r(t)dt$&#xff09;数据安全管理技术特点政务云平台支撑服务数字化率 $\eta \geq 95%$公民定义&#xff1a;主要受益者与参与者职责使用在线…

作者头像 李华
网站建设 2026/4/17 12:32:57

我们如何把“配环境一天”缩短到“3秒启动”?

我写了十年代码&#xff0c;热情被磨灭的瞬间&#xff0c;往往不是因为一个复杂的算法&#xff0c;而是因为那些无穷无尽的琐事。新同事入职&#xff0c;第一天基本废了&#xff0c;全在配环境。我的 MacBook 风扇狂转&#xff0c;就因为跑了个复杂的后端项目。最怕听到那句“在…

作者头像 李华
网站建设 2026/4/18 12:29:59

AI架构的云原生设计:AI应用架构师如何利用云服务优化架构?

AI架构的云原生设计:AI应用架构师的云端优化实战手册 关键词:AI架构、云原生、MLOps、弹性计算、分布式训练、Serverless推理、模型运维 摘要:AI系统从“实验室原型”走向“大规模生产”时,传统架构常陷入训练慢、部署难、运维繁、成本高的困境。云原生技术像一把“魔法钥匙…

作者头像 李华
网站建设 2026/4/24 22:42:10

奥偌医疗设备制造全流程解析:精工铸就医疗安全基石

一、开篇引言在现代化医疗体系中&#xff0c;安全、可靠的医疗设备是保障诊疗行为顺利进行、守护患者生命健康的关键物质基础。作为医疗气体系统解决方案的重要一环&#xff0c;奥偌医疗深知设备制造环节的至关重要性。它不仅是技术方案的物理承载&#xff0c;更是医疗安全防线…

作者头像 李华
网站建设 2026/4/23 17:14:01

选择国产CAD软件,流程顺畅比功能堆砌更实用

之前评估过不少CAD软件&#xff0c;功能列表看得人眼花缭乱&#xff0c;个个都写得天花乱坠。但对我们实际用软件干活的人来说&#xff0c;单个功能再炫酷也没用&#xff0c;要是融不进日常工作流&#xff0c;价值直接打折扣。我们要的不是一堆孤立的工具&#xff0c;是能把设计…

作者头像 李华