UnitTest框架
UnitTest
一、认识UnitTest框架
unittest 是 Python标准库中的一个测试框架,用于编写和运行单元测试。它提供了一些类和方法,用于组织测试用例、运行测试并获取结果,使用 unittest编写和运行测试用例可以帮助我们自动化测试过程,并确保代码的正确性和鲁棒性
UnitTest是Python⾃带的⼀个单元测试框架,⽤它来做单元测试
1.1 为什么使用UnitTest
- 能够组织多个⽤例去执⾏
- 提供丰富的断⾔⽅法(让程序代码代替⼈⼯⾃动的判断预期结果和实际结果是否相符)
- 能够⽣成测试报告
1.2 如何使用UnitTest
unittest 是 Python 的标准库,不需要额外安装。在你安装 Python 程序时,unittest 库就已经存在了,直接导入即可
import unittest
1.3 UnitTest的五大核心要素
测试用例(Test Cases)
:测试用例是描述要测试的特定功能或行为的代码块。每个测试用例都是一个独立的功能单元,可以针对不同的输入和条件进行测试
测试套件(Test Suites)
:测试套件是将多个测试用例组合在一起的容器,供批量运行和管理。可以使用 unittest.TestSuite() 类来创建一个测试套件,并将测试用例添加到其中
测试运行器(Test Runner)
:测试运行器是执行测试用例和报告测试结果的组件。unittest 提供了多种运行器,如 TextTestRunner、HTMLTestRunner 等,可以根据需要选择适当的运行器
测试加载(TestLoader)
:测试加载是指如何将测试用例组织和加载到测试套件中以进行执行
测试装置(Test Fixtures)
:测试装置是用于测试环境的准备和清理的代码块。它包括 setUp() 和 tearDown() 方法,在每个测试用例执行前后分别调用,用于设置和清理测试环境,以确保每个测试用例都在相同的环境中运行
二、TestCase(测试用例)
- 是⼀个代码⽂件, 在代码⽂件中 来书写真正的⽤例代码
- 代码⽂件的名字必须按照标识符的规则来书写(可以将代码的作⽤在⽂件的开头使⽤注释说明)
步骤
- 导包 (unittest)
- ⾃定义测试类
- 在测试类中书写测试⽅法
- 执⾏⽤例
# 导包
import unittest
# 自定义测试类
class MyTestCase(unittest.TestCase):
def setUp(self):
# 在每个测试用例之前执行的操作
pass
def tearDown(self):
# 在每个测试用例之后执行的操作
pass
# 书写要求, 测试⽅法 必须以 test_ 开头(本质是以test 开头)
def test_addition(self):
self.assertEqual(2 + 2, 4)
def test_subtraction(self):
self.assertEqual(5 - 3, 2)
if __name__ == '__main__':
unittest.main()
在上述示例中,MyTestCase 类继承自 unittest.TestCase
并定义了两个测试方法 test_addition 和 test_subtraction
在每个测试方法中,使用了断言方法来验证期望结果和实际结果是否相等
setUp() 和 tearDown() 方法是可选的
可以根据需要在测试前后执行一些准备和清理操作
通过运行 unittest.main(),将执行所有继承自 TestCase 的测试用例,并生成测试报告
三、 TestSuite(测试套件)与TestRunner(测试运行)
测试套件(Test Suite)是用于容纳和管理多个测试用例的对象。通过将多个测试用例组合到测试套件中,可以方便地批量执行和管理测试
步骤
- 导包(unittest)
- 实例化(创建对象)套件对象
- 使⽤套件对象添加⽤例⽅法
- 实例化运⾏对象
- 使⽤运⾏对象去执⾏套件对象
# 1. 导包(unittest)
import unittest
class MyTestCase(unittest.TestCase):
def test_addition(self):
self.assertEqual(2 + 2, 4)
def test_subtraction(self):
self.assertEqual(5 - 3, 2)
if __name__ == '__main__':
# 2. 实例化(创建对象)套件对象
suite = unittest.TestSuite()
# 3. 使⽤套件对象添加⽤例⽅法
suite.addTest(MyTestCase('test_addition'))
suite.addTest(MyTestCase('test_subtraction'))
# 4. 实例化运⾏对象
runner = unittest.TextTestRunner()
# 5. 使⽤运⾏对象去执⾏套件对象
runner.run(suite)
在上述示例中,我们创建了一个测试套件 suite
并将两个测试用例 test_addition 和 test_subtraction 添加到测试套件中
然后,我们使用 TextTestRunner 运行了测试套件
并生成了一个简单的文本测试报告
三、TestLoader(测试加载)
测试加载是指将测试用例加载到测试套件中的过程
测试加载的目的是将要执行的测试用例组织成一个测试套件,以便后续执行和生成测试报告
步骤
- 导包
- 实例化测试加载对象并添加用例 —> 得到的是 suite 对象
- 实例化 运行对象
- 运行对象执行套件对象
在一个项目中 TestCase(测试用例) 的代码,一般放在一个单独的目录 (case)
写法:suite = unittest.defaultTestLoader.discover(“指定搜索的目录文件”,“指定字母开头模块文件”)
# 1. 导包
# 2. 使用默认的加载对象并加载用例
# 3. 实例化运行对象并运行
"""TestLoader 的使用"""
# 1, 导包
import unittest
# 2, 使用默认的加载对象并加载用例
suite = unittest.defaultTestLoader.discover('case', 'hm_*.py')
# 可以将 3 4 步 变为一步
unittest.TextTestRunner().run(suite)
四、Fixture(测试夹具)
测试夹具(test fixture)是指在测试用例执行前后设置和清理测试环境所需的代码。夹具可以确保每个测试用例在独立的环境中执行,以避免测试用例之间相互影响
4.1 方法级别
setUp()和tearDown()方法:
setUp()
方法在每个测试用例执行之前调用,用于设置测试环境tearDown()
方法在每个测试用例执行之后调用,用于清理测试环境
# 方法执行之前
def setUp(self):
每个测试方法执行之前都会执行
pass
# 方法执行之后
def tearDown(self):
每个测试方法执行之后都会执行
pass
4.2 类级别
- setUpClass()方法在测试类中的所有测试用例执行之前调用一次,用于设置整个测试类的环境
- tearDownClass()方法在测试类中的所有测试用例执行之后调用一次,用于清理整个测试类的环境
- 这两个方法需要使用@classmethod装饰器进行修饰
import unittest
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
# 在测试类中的所有测试用例执行之前的准备工作
cls.data = [1, 2, 3]
@classmethod
def tearDownClass(cls):
# 在测试类中的所有测试用例执行之后的清理工作
cls.data = None
def test_something(self):
# 测试用例
self.assertEqual(len(self.data), 3)
if __name__ == '__main__':
unittest.main()
五、断言
断言(assertion)是在测试代码中用于检查是否满足特定条件的语句。它们用于验证预期结果与实际结果是否一致,从而确定测试是否通过
断言的结果有两种:
True, 用例通过
False, 代码抛出异常, 用例不通过
在 unittest 中使用断言, 都需要通过self.
断言方法 来试验
断言方法:
assertEqual(a, b)
:断言a和b是否相等
self.assertEqual(2 + 2, 4) # 断言2+2是否等于4
assertNotEqual(a, b)
:断言a和b是否不相等
self.assertNotEqual(2 + 2, 5) # 断言2+2是否不等于5
assertTrue(x)
:断言x是否为True
self.assertTrue(2 < 3) # 断言2是否小于3,结果为True
assertFalse(x)
:断言x是否为False
self.assertFalse(2 > 3) # 断言2是否大于3,结果为False
assertIs(a, b)
:断言a和b是否是同一个对象
self.assertIs(value1, value2) # 断言value1和value2是否是同一个对象
assertIsNot(a, b)
:断言a和b是否不是同一个对象
self.assertIsNot(value1, value2) # 断言value1和value2是否不是同一个对象
assertIn(a, b)
:用于断言a是否存在于b中
self.assertIn(item, list) # 断言item是否存在于list中
import unittest
class Calculator:
def add(self, a, b):
return a + b
class TestCalculator(unittest.TestCase):
"""测试计算器功能"""
def setUp(self):
self.calculator = Calculator()
def tearDown(self):
pass
def test_addition(self):
"""测试加法"""
result = self.calculator.add(2, 3)
self.assertEqual(result, 5)
def test_invalid_input(self):
"""测试无效输入"""
with self.assertRaises(TypeError):
self.calculator.add('2', 3)
if __name__ == '__main__':
unittest.main()
-
在上述示例中,我们首先定义了一个简单的Calculator类
-
该类仅具有一个add方法,用于执行加法操作
-
然后,我们编写了一个名为TestCalculator的测试类,继承自unittest.TestCase
-
在该测试类中,我们定义了两个测试方法test_addition和test_invalid_input
-
分别用于测试加法操作的正确性和处理无效输入的情况
-
在每个测试方法之前,我们使用setUp方法在测试之前创建了一个Calculator实例
-
并在tearDown方法中进行清理工作
-
这可以确保每个测试方法在独立的环境中运行,防止测试之间相互影响
请注意,assertRaises方法用于检测是否引发了特定的异常。在test_invalid_input方法中,我们使用了该方法来检查在给定无效输入的情况下是否引发了TypeError异常
六、跳过
对于一些未完成的或者不满足测试条件的测试函数和测试类, 不想执行,可以使用跳过
使用方法, 装饰器完成
代码书写在 TestCase 文件
# 直接将测试函数标记成跳过
@unittest.skip('跳过额原因')
# 根据条件判断测试函数是否跳过 , 判断条件成立, 跳过
@unittest.skipIf(判断条件, '跳过原因')
import unittest
class MyTestCase(unittest.TestCase):
def test_example(self):
self.assertEqual(2 + 2, 4)
@unittest.skip("示例跳过")
def test_skip_example(self):
self.assertEqual(2 * 2, 5)
@unittest.skipIf(True, "条件为 True,跳过")
def test_skip_if_example(self):
self.assertEqual(2 / 0, 1)
if __name__ == '__main__':
unittest.main()
在上面的示例中,我们定义了三个测试方法
test_example 方法是一个正常的测试用例,不会被跳过
test_skip_example 方法上使用了 @unittest.skip 装饰器,因此它被标记为跳过,不会执行
test_skip_if_example 方法上使用了 @unittest.skipIf 装饰器,并且设置了条件为 True
因此,这个测试用例会根据条件是否满足来决定是否被跳过
在这个示例中,条件为 True,所以这个测试用例也会被跳过
七、参数化
参数化 在测试方法中, 使用 变量 来代替具体的测试数据, 然后使用传参的方法将测试数据传递给方法的变量
好处: 相似的代码不需要多次书写
安装插件
联网安装(在 cmd 窗口安装 或者 )
pip install parameterized
可以使用 @unittest.parameterized.parameterized 装饰器来实现参数化测试
- 导包 unittest/ pa
- 定义测试类
- 书写测试方法(用到的测试数据使用变量代替)
- 组织测试数据并传参
import unittest
from parameterized import parameterized
class Calculator:
def add(self, a, b):
return a + b
class TestCalculator(unittest.TestCase):
"""测试计算器功能"""
@parameterized.expand([
(2, 3, 5),
(0, 0, 0),
(-1, 4, 3),
(10, -5, 5)
])
def test_addition(self, a, b, expected_result):
"""测试加法"""
calculator = Calculator()
result = calculator.add(a, b)
self.assertEqual(result, expected_result)
if __name__ == '__main__':
unittest.main()
- 示例中,我们首先定义了一个 Calculator 类,其中有一个 add 方法用于执行加法运算
- 然后,在 TestCalculator 类中,我们对 test_addition 方法进行了参数化
- 我们使用 @parameterized.expand 装饰器来指定参数化的数据集
- 数据集是一个列表,其中每个元素代表一个测试案例的参数组合
- 而在每个元素内部,我们可以指定多个参数值,以及期望的结果值
- 在 test_addition 方法的参数列表中,我们定义了三个参数 a、b 和 expected_result
- 并且它们会按照数据集中的顺序进行传
八、测试报告
1、自带的测试报告
只有单独运行 TestCase 的代码,才会生成测试报告
2、生成第三方的测试报告
- 获取第三方的 测试运行类模块 , 将其放在代码的目录中
- 导包 unittest
- 使用 套件对象, 加载对象 去添加用例方法
- 实例化 第三方的运行对象 并运行 套件对象
当我们使用 HTMLTestRunner 生成 HTML 格式的测试报告时,需要下载 HTMLTestRunner 插件并导入它,然后通过创建测试套件并运行测试来生成测试报告
import unittest
from HTMLTestRunner import HTMLTestRunner
class MyTestCase(unittest.TestCase):
def test_example(self):
self.assertEqual(2 + 2, 4)
def test_another_example(self):
self.assertEqual(3 * 3, 9)
if __name__ == '__main__':
# 创建测试套件
suite = unittest.TestSuite()
suite.addTest(MyTestCase('test_example'))
suite.addTest(MyTestCase('test_another_example'))
# 指定报告文件路径
report_file = 'test_report.html'
# 使用 HTMLTestRunner 运行测试并生成报告
with open(report_file, 'wb') as f:
runner = HTMLTestRunner(stream=f, title='测试报告', description='执行结果如下:')
runner.run(suite)
- 在这个示例中,我们定义了两个测试方法 test_example 和 test_another_example
- 然后,我们创建了一个测试套件 suite,并将这两个测试方法添加到套件中
- 接下来,我们指定了报告文件的路径 report_file,这里设置为 test_report.html
- 然后,我们使用 HTMLTestRunner 运行测试并生成报告
- 我们通过 open 函数打开文件并将其作为参数传递给 HTMLTestRunner 的 stream 参数
- 这样生成的报告将会被写入到这个文件中
- title 参数用于设置报告的标题,description 参数用于设置报告的描述
- 最后,我们运行测试套件,并将测试结果生成为 HTML 格式的报告