unittest原名为PyUnit,是由java的JUnit衍生而来。unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件。它受到 Java 的 JUnit 框架的启发,提供了丰富的断言方法、测试套件(Test Suite)、测试夹具(Fixture)等功能,适合对代码进行自动化测试。
Unittest的5个概念
- test case:测试用例,一个完整的测试单元,执行该测试单元可以完成对某一个问题的验证
- test suite:测试套件,多个测试用例的集合,测试套件或测试计划;
- TestLoader:批量执行测试用例,加载test case到test suite中的
- test fixure:测试夹具,一个测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;
- test runner: 用来执行测试用例和测试套件,并返回测试用例的执行结果
Unittest的使用
TestCase 测试用例
在设计用例的时候,会继承 unittest 当中的 TestCase 的类和方法,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例,unittest中的测试用例都是以 “test” 开头,并且它的执行顺序是按照方法名的ASCII值进行排序。
注意:在创建TestCase时,如果有构造方法会报错,无法创建构造方法,但是可以创建析构方法。用例方法是根据ascii码的顺序执行的,pytest的用例是谁在前谁先执行。unittest只有方法用例,测试类必须集成TestCase类
举例:
import unittest
class Test_Case_01(unitest.TestCase): # 新建测试类必须继承unittest.TestCase
# 测试方法名称必须以 test 开头
def test_01(self):
print("测试用例名称为:test_01")
def test_02(self):
print("测试用例名称为:test_02")
if __name__ == '__main__':
# 执行测试用例
unittest.main()
TestSuite测试套件
一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了TestSuite的概念。我们可以把它理解成一个篮子,篮子里面放了很多东西(test case)。使用addTest来加载TestCase到TestSuite中。
使用:
实例化:suite = unittest.TestSuite()
添加用例:suite.addTest(ClassName(“MethodName”)) #(ClassName:类名,MethodName:方法名)
举例:
import unittest
from test_001 import Test_Case_01
# 实例化测试套件对象
suite = unittest.TestSuite()
# 单独添加测试用例
suite.addTest(Test_Case_01("test_01"))
suite.addTest(Test_Case_01("test_02"))
# 执行
runner = unittest.TextTestRunner()
runner.run(suite)
TestRunner执行测试用例和测试套件
用来执行测试用例和测试套件,并返回测试用例的执行结果。在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case。
TextTestRunner使用:
实例化:runner = unittest.TextTestRunner()
执行:runner.run(suite) # suite为测试套件的名称,可以参考上文的代码
TestLoader加载TestCase
用来加载TestCase到TestSuite中,即加载满足条件的测试用例,并把测试用例封装成测试套件。使用unittest.TestLoader(),通过该类下面的discover()方法自动搜索指定目录下指定开头的 .py 文件,并将查到的测试用例组装到测试套件。
举例:
import os
import unittest
suit = unittest.TestSuite()
loader = unittest.TestLoader() # 专门用来查找用例的实例
# test_case_path: 测试用例的所在路径。pattern:搜索以test开头的py文件,默认为‘test*.py’
suit.addTests(loader.discover(test_case_path, pattern="test*.py"))
runner = unittest.TestRunner(。。。)
runner.run(suit)
TestSuite和TestLoader的区别
共同点:都是测试套件
不同点:实现方式不同
(1) TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中的某个测试方法)
(2) TestLoader搜索指定目录下指定开头的py文件,并添加测试类中的所有测试方法,不能指定添加某个测试方法
unittest夹具(固件)
虽然unittest是Python标准库中的测试框架,不像Pytest那样有显式的@pytest.fixture装饰器,但它通过setUp/tearDown方法和setUpClass/tearDownClass等机制实现了类似夹具的功能。以下是unittest中夹具的用法和原理:
unittest的夹具机制
| 方法 | 作用 | 执行时机 |
|---|---|---|
setUp() | 在每个测试方法(test_*)前运行,用于初始化资源(类似 pytest 的 function 作用域夹具)。 | 每个测试方法前调用 |
tearDown() | 在每个测试方法后运行,用于清理资源。 | 每个测试方法后调用 |
setUpClass() | 在整个测试类前运行(需用 @classmethod 装饰),类似 pytest 的 class 作用域夹具。 | 类中第一个测试方法前调用一次 |
tearDownClass() | 在整个测试类后运行(需用 @classmethod 装饰),用于类级清理。 | 类中最后一个测试方法后调用一次 |
setUpModule() | 在整个测试模块(文件)前运行(模块级全局函数),类似 pytest 的 module 作用域。 | 模块的第一个测试类前调用一次 |
tearDownModule() | 在整个测试模块后运行(模块级全局函数),用于模块级清理。 | 模块的最后一个测试类后调用一次 |
执行顺序
setUpModule()
↓
setUpClass() → TestCase1 → tearDownClass()
↓
setUpClass() → TestCase2 → tearDownClass()
↓
tearDownModule()
举例:
import time
import unittest
from selenium import webdriver
class Test_Tmall_Login(unittest.TestCase):
def setUp(self) -> None:
# 获取Chrome浏览器驱动对象
self.driver = webdriver.Chrome()
# 打开url
self.driver.get("http://182.44.27.72:8090/korei/login.jsp")
# 窗口最大化
self.driver.maximize_window()
# 隐式等待
self.driver.implicitly_wait(30)
def tearDown(self) -> None:
# 等待3秒,关闭浏览器
time.sleep(3)
self.driver.quit()
def test_login(self):
driver = self.driver
# 输入用户名
driver.find_element(By.NAME, "username").send_keys("admin")
# 输入密码
driver.find_element(By.NAME, "password").send_keys("1")
# 点击登录按钮
driver.find_element(By.XPATH, '//*[text()="Log in"]').click()
unittest装饰器
Python 的 Unittest 模块提供了一些内置装饰器,用于控制测试行为(如跳过测试、预期失败等)。以下是主要装饰器及其用法:
测试跳过装饰器
@unittest.skip(reason):无条件跳过当前测试。
import unittest
class TestDemo(unittest.TestCase):
@unittest.skip("功能暂未实现")
def test_old_feature(self):
self.fail("不应执行")
def test_new_feature(self):
self.assertEqual(1 + 1, 2)
@unittest.skipIf(condition, reason):当条件为 True 时跳过。
import sys
class TestSkipIf(unittest.TestCase):
@unittest.skipIf(sys.version_info < (3, 8), "需要 Python 3.8+")
def test_python3_8_feature(self):
self.assertTrue(True)
@unittest.skipUnless(condition, reason):当条件为 False 时跳过。
import os
class TestSkipUnless(unittest.TestCase):
@unittest.skipUnless(os.getenv("RUN_INTEGRATION_TESTS"), "需设置环境变量")
def test_integration(self):
self.assertTrue(True)
预期失败装饰器
@unittest.expectedFailure:标记测试为“预期失败”,如果测试通过反而报错。
class TestExpectedFailure(unittest.TestCase):
@unittest.expectedFailure
def test_buggy_code(self):
self.assertEqual(1, 2) # 预期失败,不报错
@unittest.expectedFailure
def test_fixed_code(self):
self.assertEqual(1, 1) # 意外通过,触发异常
. 表示通过 或者 pass
S 表示跳过
x 表示忽略错误
F False, 表示断言没有通过
E Error, 表示程序内部发生了错误
参数化测试装饰器
unittest 本身不支持原生参数化,但可通过以下方式实现
@parameterized.expand(可迭代类型数据)
from parameterized import parameterized
class TestMath(unittest.TestCase):
@parameterized.expand([
("positive", 1, 1, 2),
("negative", -1, -1, -2),
])
def test_add(self, name, a, b, expected):
self.assertEqual(a + b, expected)
输出
test_add_0_positive (__main__.TestMath) ... ok
test_add_1_negative (__main__.TestMath) ... ok
- 使用用例的形参接收可迭代类型的元素
- 如果可迭代类型数据不是二维使用一个形参接收
- 如果可迭代类型数据是二维的,必须使用多个形参接收数据,形参个数为容器的元素个数
@ddt.data(使用 ddt 库)
import ddt
@ddt.ddt
class TestDDT(unittest.TestCase):
@ddt.data((1, 1), (2, 3))
@ddt.unpack
def test_less_than(self, a, b):
self.assertLess(a, b)
其他实用装饰器
@mock.patch(模拟对象,来自 unittest.mock)
from unittest.mock import patch
class TestMock(unittest.TestCase):
@patch("os.getcwd") # 模拟 os.getcwd()
def test_mock(self, mock_getcwd):
mock_getcwd.return_value = "/fake/path"
self.assertEqual(os.getcwd(), "/fake/path")
Unittest断言
unittest提供了一系列断言方法(Assertions),用于验证测试结果是否符合预期。
| 断言 | 例子 | 说明 |
|---|---|---|
| assertEqual(a, b) | a==b | 值是否相等 |
| assertNotEqual(a, b) | a!=b | 值是否不相等 |
| assertIs(a,b) | a is b | 值是否相同 |
| aassertIsNot(a,b) | a is not b | 值是否不同 |
| assertIn(a,b) | a in b | a是否包含b |
| assertNotIn(a,b) | a not in b | a是否不包含b |
| assertTrue(a) | bool(a) | is true 是否为真 |
| assertFalse(a) | bool(a) | is false 是否为假 |
| assertIsNone(a) | a is None | 是否为空 |
| assertIsNotNone(a) | a is None | 是否不为空 |
| assertIsInstance(a,b) | Instance(a,b) | a与b的数据类型一样 |
| assertNotIsInstance(a) | not Instance(a,b) | a与b的数据类型不一样 |
举例:
class Testdemo(unittest.TestCase)
def test_method3(self):
print("用例方法3")
self.assertIn(1,[1,2,3])
def test_method1(self):
print("用例方法1")
assert 1==1 ,"描述信息"
输出报告
使用HTMLTestRunner
pip install html-testRunner
import unittest
from unittestreport import TestRunner
suite = unittest.TestSuite()
loder = unittest.TestLoader()
suite.addTests(loder.discover(r'用例目录', pattern='用例.py'))
#runner1 = TestRunner(suite)
runner1 = TestRunner(
suite=suite, #指明运行的测试套
filename="report.html", #确定生成报告的名称
report_dir="./reports" #报告存放的路径
title="测试报告"
tester="测试员"
desc="xxx项目测试生成的报告"
templates=1
)
runner1.run()
使用BeautifulReport
pip install BeautifulReport
import unittest
from BeautifulReport import BeautifulReport
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(1 + 1, 2)
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(TestMath)
result = BeautifulReport(suite)
result.report(
description="单元测试报告",
filename="unittest_report.html",
log_path="reports"
)

Comments NOTHING