news 2026/4/15 5:40:37

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/13 7:29:26

基于Simulink的混合PO与INC切换MPPT策略仿真

目录 手把手教你学Simulink 一、引言&#xff1a;为什么需要“混合P&O与INC”&#xff1f; 二、系统整体架构 控制流程&#xff1a; 三、核心算法详解 1. P&O 算法&#xff08;扰动观察法&#xff09; 2. INC 算法&#xff08;电导增量法&#xff09; 3. 光照突…

作者头像 李华
网站建设 2026/4/14 4:16:41

工业场景中弧形导轨的安装要点

弧形导轨作为工业自动化中实现弧线运动的核心部件&#xff0c;常用于机械臂关节、旋转工作台、自动化生产线转弯部位&#xff0c;医疗CT机的旋转扫描部件也依赖高精度弧形导轨实现平滑运动。其安装质量直接影响设备运行精度与寿命&#xff0c;从材料准备到定位调试&#xff0c;…

作者头像 李华
网站建设 2026/4/13 3:29:31

2026年你应该掌握的进阶版 Gemini CLI 实用指南

现在用AI&#xff0c;只会在网页版当聊天机器人用吗&#xff1f;那你就out了。 Gemini CLI 是 Google 推出的终端 AI 助手。相比于网页版&#xff0c;命令行工具在处理本地文件、读取项目上下文方面有着天然优势。对于开发者而言&#xff0c;它不仅仅是一个聊天机器人&#xf…

作者头像 李华
网站建设 2026/4/15 19:45:19

ppo 找出口模型 训练 笔记 26/1/13

cnn模型大小我给他控制在训练耗时30s左右&#xff08;4060ti&#xff09; 动作空间6个&#xff1a;4个移动2个转头&#xff0c;因为一开始都要跑一遍&#xff0c;动作太多需要跑更多步才能吃到正反馈 我现在设置是60步一episode&#xff0c;5 episode训练一次 转头70度左右&…

作者头像 李华
网站建设 2026/4/15 9:38:12

深度学习计算机毕设之基于python-AI机器学习对狗表情训练识别基于python-AI深度学习对狗表情训练识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华