文章目录
- UnitTest 简介
- UnitTest 核心
- UnitTest 原理
- UnitTest 断言函数
- TestCase(用例)
- 基本用法
- 执行结果
- TestFixture(夹具)
- 方法级夹具
- 类级夹具
- 模块级夹具
- TestSuite(套件)
- TestLoader(加载器)
- loadTestsFromTestCase(testCaseClass)
- loadTestsFromModule(module, pattern=None)
- loadTestsFromNames(names, module=None)
- suiteClass
- sortTestMethodsUsing(method)
- getTestCaseNames(testCaseClass)
- Discover (加载器)
- 示例代码
- TextTestRunner(运行器)
- 基本用法
- 自定义输出格式
- 使用缓冲区
- 自定义结果类
- 跳过特定测试用例
- 示例代码
- 执行结果
- BeautifulReport - 运行器扩展
- 安装
- 示例代码
- 执行结果
- unittestreport - 运行器扩展
- 安装
- 示例代码
- 执行结果
- DDT - 参数化扩展
- 什么是DDT(数据驱动)
- 为什么要使用数据驱动
- 安装
- 示例代码
- parameterized - 参数化扩展
- 安装
- 示例代码
UnitTest 简介
在Python的标准库中,
unittest
框架占据了举足轻重的地位,它是开发者们进行单元测试时不可或缺的利器。
它集结了丰富的断言方法与全面的测试工具集,从测试固件、测试套件到测试加载器,无一不为编写清晰、可维护的测试用例提供了强大的后盾。通过
unittest
,开发者们可以构建出一套坚实可靠的测试体系,确保代码的正确性和可靠性。
其断言方法的多样性和灵活性,使得测试用例的编写变得游刃有余;
其全面的测试工具集则帮助开发者们更高效地管理、执行和分析测试。不仅如此,unittest还能与各种第三方工具无缝对接,为开发者们提供更丰富的测试报告和覆盖度分析。
在unittest的支持下,软件开发的整体效率得到了显著提升,代码的质量和稳定性也得到了有力保障。
UnitTest 核心
- TestCase(测试用例):每个测试用例实例用于封装一个或多个测试函数。
- TestSuite(测试套件):多个测试用例的集合,用于组织和执行多个测试用例。
- TestLoader(测试加载器):用于将测试用例加载到测试套件中的工具。
- TextTestRunner(测试运行器):用于执行测试用例的运行器,负责运行测试并生成结果报告。
- TestFixture(测试夹具):测试用例的环境搭建和销毁部分,包括前置条件和后置条件。
UnitTest 原理
1.设置测试环境(TestFixture):
- 通过
setUp()
方法设置测试用例执行前需要的环境或条件;- 如初始化对象、打开文件、建立数据库连接等。
- 通过
tearDown()
方法设置测试用例执行后需要的环境清理;- 如关闭文件、断开数据库连接、删除数据库记录等。
2.编写测试用例(TestCase):
- 定义继承自
unittest.TestCase
的类,并在其中编写具体的测试方法;- 每个测试方法通常以
test
开头,并且不接受任何参数。3.收集测试用例(TestSuite):
- 使用
TestLoader
来加载和收集测试用例,形成一个TestSuite
;TestSuite
是一个测试用例的集合,可以包含多个TestCase
实例。4.执行测试用例(TestRunner):
TestRunner
负责执行TestSuite
中的测试用例;- 它运行每个测试用例,并收集测试结果。
5.生成测试报告:
TestRunner
根据测试结果生成报告,通常包括测试通过的用例数、测试失败的用例数、错误信息等内容。使用TestLoader无需再使用TestSuite,直接收集用例后去送给TestRunner去执行
UnitTest 断言函数
断言方法 | 断言描述 |
---|---|
assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail |
assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail |
assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail |
assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail |
assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fail |
assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
assertIsInstance(obj, cls, msg=None) | 验证obj是cls的实例,不是则fail |
assertNotIsInstance(obj, cls, msg=None) | 验证obj不是cls的实例,是则fail |
assertRaises(expr) | 验证expr异常类型,结合with使用例子:with self.assertRaises(Exception): # 测试内容 |
TestCase(用例)
在
unittest
框架中,TestCase
是一个基础的抽象类,用于定义单个的测试用例。每个
TestCase
实例代表一个具体的测试,它会检查某个特定的功能或行为是否符合预期。使用规则:
- 测试用例必须在
类
中- 测试用例所在的类,必须继承自
unittest.TestCase
- 测试用例的方法名称,必须以
test
开头- 测试用例的方法参数,通常除了
self
外不接受其他参数- 测试用例的执行顺序是按照
ASCILL
编码表的排列(0-9,A-Z,a-z…)
基本用法
# 第一步:导入unittest模块
import unittest
# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):
# 第三步:定义测试函数,函数名以test_开头。测试用例
def test_1(self):
print("这是第一条测试案例...")
def test_2(self):
print("这是第二条测试案例...")
def test_3(self):
print("这是第三条测试案例...")
def test_4(self):
print("这是第四条测试案例...")
# 第四步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
unittest.main()
执行结果
TestFixture(夹具)
方法级夹具
# 第一步:导入unittest模块
import unittest
# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):
# 第三步:重写父类的setUp和tearDown方法
# 这是每个test case执行前的初始化
def setUp(self):
print("setUp方法执行了...")
# 这是每个test case执行后的测试回收
def tearDown(self):
print("tearDown方法执行了...")
# 第四步:定义测试函数,函数名以test_开头。测试用例
def test_1(self):
print("这是第一条测试案例...")
def test_2(self):
print("这是第二条测试案例...")
def test_3(self):
print("这是第三条测试案例...")
def test_4(self):
print("这是第四条测试案例...")
# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
unittest.main()
类级夹具
# 第一步:导入unittest模块
import unittest
# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):
# 第三步:重写父类的setUp和tearDown方法
# 这是TestCases执行前的初始化
@classmethod
def setUpClass(cls):
print("测试类 Begin...")
# 这是TestCases执行后的回收操作
@classmethod
def tearDownClass(cls):
print("测试类 End...")
# 这是每个test case执行前的初始化
def setUp(self):
print("setUp方法执行了...")
# 这是每个test case执行后的测试回收
def tearDown(self):
print("tearDown方法执行了...")
# 第四步:定义测试函数,函数名以test_开头。测试用例
def test_1(self):
print("这是第一条测试案例...")
def test_2(self):
print("这是第二条测试案例...")
def test_3(self):
print("这是第三条测试案例...")
def test_4(self):
print("这是第四条测试案例...")
# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
unittest.main()
模块级夹具
# 第一步:导入unittest模块
import unittest
# 模块级别的需要写在 类的外边 直接 定义函数 即可
def setUpModule():
print("模块前置:", __name__)
def tearDownModule():
print("模块后置:", __name__)
# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo1(unittest.TestCase):
# 第三步:重写父类的setUp和tearDown方法
# 这是TestCases执行前的初始化
@classmethod
def setUpClass(cls):
print("测试类 Begin...")
# 这是TestCases执行后的回收操作
@classmethod
def tearDownClass(cls):
print("测试类 End...")
# 这是每个test case执行前的初始化
def setUp(self):
print("setUp方法执行了...")
# 这是每个test case执行后的测试回收
def tearDown(self):
print("tearDown方法执行了...")
# 第四步:定义测试函数,函数名以test_开头。测试用例
def test_1(self):
print("这是第一条测试案例...")
def test_2(self):
print("这是第二条测试案例...")
def test_3(self):
print("这是第三条测试案例...")
def test_4(self):
print("这是第四条测试案例...")
# 将unittest_demo1拷贝一份
class unittest_demo2(unittest.TestCase):
# 第三步:重写父类的setUp和tearDown方法
# 这是TestCases执行前的初始化
@classmethod
def setUpClass(cls):
print("测试类 Begin...")
# 这是TestCases执行后的回收操作
@classmethod
def tearDownClass(cls):
print("测试类 End...")
# 这是每个test case执行前的初始化
def setUp(self):
print("setUp方法执行了...")
# 这是每个test case执行后的测试回收
def tearDown(self):
print("tearDown方法执行了...")
# 第四步:定义测试函数,函数名以test_开头。测试用例
def test_1(self):
print("这是第一条测试案例...")
def test_2(self):
print("这是第二条测试案例...")
def test_3(self):
print("这是第三条测试案例...")
def test_4(self):
print("这是第四条测试案例...")
# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
unittest.main()
TestSuite(套件)
在
unittest
框架中,测试套件(Test Suite)是一个重要的概念;测试套件允许将多个测试用例、测试套件或测试函数组合到一起,然后一次性运行它们。
用途:
- 将多个测试用例组合在一起,形成一个逻辑上的测试组。
- 分层组织测试,例如先运行快速的基础测试,再运行更复杂的集成测试。
- 动态地添加或移除测试用例。
- 重复运行同一组测试。
优点:
- **组织性:**测试套件允许以结构化的方式组织测试用例,使得测试代码更加清晰和易于维护。
- **灵活性:**可以根据需要动态地添加、删除或修改测试套件中的测试用例。
- **复用性:**测试套件可以重复使用,例如在不同的测试环境或不同的测试阶段中。
- **批量执行:**通过测试套件,可以一次性执行多个测试用例,而无需单独运行每个测试用例。
函数:
- **addTest(test):**将一个测试用例(
unittest.TestCase
的实例)或测试套件(TestSuite
的实例)添加到测试套件中。- **addTests(tests):**将多个测试用例或测试套件添加到测试套件中。
tests
参数通常是一个测试用例或测试套件的列表或元组。- **countTestCases():**返回测试套件中包含的测试用例数量。
- **run(result):**运行测试套件中的所有测试用例,并将结果存储在
result
参数中(通常是一个unittest.TestResult
的实例)。
import unittest
# 定义第一个测试用例类
class TestClass1(unittest.TestCase):
def test_1_01(self):
print("执行 test_1_01 测试用例...")
# 使用断言函数,判断两个参数相等
self.assertEqual(1 + 1, 2)
def test_1_02(self):
print("执行 test_1_02 测试用例...")
# 使用断言函数,判断一个参数是不是True
self.assertTrue(True)
# 定义第二个测试用例类
class TestClass2(unittest.TestCase):
def test_2_01(self):
print("执行 test_2_01 测试用例...")
# 使用断言函数,判断参数1是不是参数2的子串
self.assertIn("hello", "hello world")
def test_2_02(self):
print("执行 test_2_02 测试用例...")
# 使用断言函数,判断参数是不是None
self.assertIsNone(None)
if __name__ == '__main__':
# 创建测试套件
suite = unittest.TestSuite()
# 将第一个测试用例类的所有测试方法添加到测试套件中
suite.addTest(unittest.makeSuite(TestClass1))
# 将第二个测试用例类的一个特定测试方法添加到测试套件中
suite.addTest(TestClass2("test_2_02"))
# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
TestLoader(加载器)
unittest框架中的TestLoader是一个测试用例加载器;
TestLoader
的主要任务是发现和加载测试用例,以便之后执行。
TestLoader
提供了几个方法用于加载测试用例,并可以根据不同的需求选择使用。
loadTestsFromTestCase(testCaseClass)
用于加载一个特定的测试用例类中的所有测试方法
并将它们作为TestSuite
返回
import unittest
class MyTestCase(unittest.TestCase):
def test_method1(self):
self.assertEqual(1 + 1, 2)
def test_method2(self):
self.assertEqual(2 * 2, 4)
# 创建TestLoader实例
loader = unittest.TestLoader()
# 使用loadTestsFromTestCase加载测试用例类中的所有测试方法
suite = loader.loadTestsFromTestCase(MyTestCase)
# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
loadTestsFromModule(module, pattern=None)
用于加载一个模块中所有符合指定模式的测试用例。
如果不提供pattern
,则加载模块中的所有测试用例
import unittest
# 假设有一个名为my_module的模块,其中包含测试用例
# my_module.py
class TestMyModule(unittest.TestCase):
def test_something(self):
self.assertEqual(1, 1)
# 在主脚本中加载模块中的测试用例
loader = unittest.TestLoader()
# 加载my_module模块中的所有测试用例
suite = loader.loadTestsFromModule(my_module)
# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
loadTestsFromNames(names, module=None)
根据提供的名称列表加载测试用例;
这些名称可以是测试用例类名、模块名或可调用对象(例如函数)的名称。
import unittest
# 假设有两个测试用例类
class TestA(unittest.TestCase):
def test_a(self):
self.assertEqual(1, 1)
class TestB(unittest.TestCase):
def test_b(self):
self.assertEqual(2, 2)
# 创建TestLoader实例
loader = unittest.TestLoader()
# 使用loadTestsFromNames加载特定的测试用例类
suite = loader.loadTestsFromNames(['__main__.TestA', '__main__.TestB'])
# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
suiteClass
这是一个类属性,它指定了当
TestLoader
创建新的TestSuite
实例时所使用的类。通常不需要直接设置这个属性,除非你想要自定义
TestSuite
的行为。
class CustomSuite(unittest.suite.TestSuite):
# 自定义TestSuite的行为
pass
class CustomTestLoader(unittest.TestLoader):
suiteClass = CustomSuite # 指定使用自定义的TestSuite类
# 使用自定义的TestLoader加载测试用例
loader = CustomTestLoader()
suite = loader.loadTestsFromTestCase(MyTestCase)
sortTestMethodsUsing(method)
这个方法允许指定一个排序函数;
用于对TestCase
类中的测试方法进行排序。
def sort_test_methods(a, b):
# 自定义排序函数
return a[3:] < b[3:] # 按方法名字母顺序排序,忽略'test_'前缀
loader = unittest.TestLoader()
loader.sortTestMethodsUsing(sort_test_methods)
suite = loader.loadTestsFromTestCase(MyTestCase)
runner = unittest.TextTestRunner()
runner.run(suite)
getTestCaseNames(testCaseClass)
这个方法返回一个列表,其中包含
testCaseClass
中所有以test
开头的方法的名称。这个方法通常不直接调用,而是由
TestLoader
在内部使用来确定哪些方法应该作为测试用例加载。
loader = unittest.TestLoader()
Discover (加载器)
discover()是unittest框架中的一个功能强大的函数,用于自动发现和运行测试。
该函数会根据给定的开始目录(起始路径),递归地查找该目录及其子目录下的所有测试模块,并加载它们执行测试。discover() 的用途:
- 自动发现测试模块:在大型项目中,测试可能分布在多个文件或目录中。
discover()
函数可以自动找到这些测试模块,而无需手动导入每个模块。- 灵活性和可扩展性:通过指定不同的参数,如模式匹配、顶层目录等,
discover()
可以非常灵活地控制哪些测试应该被执行。discover()的参数:
- **start_dir:**要搜索测试模块的起始目录
- **pattern:**匹配测试模块名的模式(默认为
test*.py
)- **top_level_dir:**用于确定要运行的测试的顶层目录(默认为
None
,表示使用start_dir
作为顶层目录)- **verbosity:**输出详细信息的级别(默认为 1,表示输出简要结果)
- **buffer:**控制输出的缓冲(默认为
False
)- **suiteClass:**用于创建测试套件的类(默认为
unittest.TestSuite
)- **loaderClass:**用于加载测试模块的类(默认为
unittest.TestLoader
)
示例代码
假设有以下目录结构:
project_root/
│
├── tests/
│ ├── test_module1.py
│ └── test_module2.py
│
└── main.py在
test_module1.py
和test_module2.py
中分别定义了测试用例。可以使用以下代码来运行所有测试:
import unittest
# 使用 discover() 函数自动发现并运行测试
unittest.discover(start_dir='./tests', pattern='test*.py')
# 这将运行 tests 目录下所有以 test 开头且扩展名为 .py 的文件中的测试
TextTestRunner(运行器)
TextTestRunner是unittest框架中的一个类,它提供了一个文本模式的测试执行器,用于运行测试用例并输出结果;
TextTestRunner
可以配置以显示详细的测试输出,包括测试进度、测试成功或失败的信息等。
TextTestRunner
的构造函数接受几个参数来定制其行为:
- **stream:**一个输出流对象,用于写入测试结果。默认为
sys.stdout
,表示标准输出。- **descriptions:**一个布尔值,指定是否打印每个测试用例的描述。默认为
True
。- **verbosity:**一个整数,指定测试输出的详细程度。0表示最少输出,1表示正常输出,2表示详细输出。默认为1。
- **failfast:**一个布尔值,如果为
True
,则一旦有测试失败,就立即停止运行后面的测试。默认为False
。- **buffer:**一个布尔值,如果为
True
,则输出会被缓冲,直到测试运行结束。默认为False
。- **resultclass:**一个
TestResult
类的子类,用于处理测试结果。默认为unittest.TextTestResult
。
基本用法
TextTestRunner
的主要方法是run(test)
;
它接受一个TestSuite
或TestCase
实例作为参数;
运行测试并返回测试结果。
import unittest
# 定义测试用例类
class MyTestCase(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
def test_subtraction(self):
self.assertEqual(2 - 1, 1)
# 创建测试套件
suite = unittest.TestSuite()
suite.addTest(MyTestCase('test_addition'))
suite.addTest(MyTestCase('test_subtraction'))
# 创建TextTestRunner实例
runner = unittest.TextTestRunner()
# 运行测试套件并输出结果
result = runner.run(suite)
# 输出测试结果
print("Test result:", result.wasSuccessful())
自定义输出格式
可以通过修改
TextTestRunner
的verbosity
参数来定制输出信息的详细程度。
runner = unittest.TextTestRunner(verbosity=2)
# verbosity=0: 最简洁的输出
# verbosity=1: 标准输出
# verbosity=2: 详细输出
使用缓冲区
buffer
参数可以决定输出是否缓冲;
如果设置为True
,则所有输出都会被缓冲,直到测试运行完成
runner = unittest.TextTestRunner(buffer=True)
自定义结果类
resultclass
参数允许你使用自定义的结果类来处理测试结果
class CustomResult(unittest.TextTestResult):
# 在这里可以添加自定义逻辑
pass
runner = unittest.TextTestRunner(resultclass=CustomResult)
跳过特定测试用例
使用
@unittest.skip(reason)
强制跳过测试用例,并提供一个跳过的原因
import unittest
class MyTestCases(unittest.TestCase):
@unittest.skip("这个测试用例暂时不被需要或尚未完成")
def test_skipped_due_to_reason(self):
self.assertTrue(False, "这个测试不会执行,因为它被跳过了")
if __name__ == '__main__':
unittest.main()
使用
@unittest.skipIf(condition, reason)
条件成立时跳过测试用例,并提供一个跳过的原因
import unittest
import sys
class MyTestCases(unittest.TestCase):
@unittest.skipIf(sys.version_info < (3, 6), "这个测试需要Python 3.6或更高版本")
def test_python_version(self):
self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")
if __name__ == '__main__':
unittest.main()
使用
@unittest.skipUnless(condition, reason)
条件不成立时跳过测试用例,并提供一个跳过的原因
import unittest
class MyTestCases(unittest.TestCase):
@unittest.skipUnless(False, "这个测试被故意设置为总是跳过")
def test_always_skipped(self):
self.assertTrue(True, "这个测试不会执行,因为它被跳过了")
if __name__ == '__main__':
unittest.main()
使用
@unittest.expectedFailure
标记一个测试用例预期失败
import unittest
class MyTestCases(unittest.TestCase):
@unittest.expectedFailure
def test_expected_to_fail(self):
self.assertTrue(False, "这个测试预期会失败,因为它尚未完成或存在已知问题")
if __name__ == '__main__':
unittest.main()
使用
self.skipTest()
函数,跳过当前正在执行的测试用例这通常在测试方法的内部逻辑中基于某些条件决定跳过测试时使用。
import unittest
class MyTestCases(unittest.TestCase):
def test_skipping_in_method(self):
# 基于某种条件决定跳过测试
if not some_external_condition: # 假设 some_external_condition 是一个外部条件或变量
self.skipTest("由于某些外部条件,跳过此测试")
self.assertTrue(True, "这个测试执行了")
if __name__ == '__main__':
unittest.main()
示例代码
import unittest
import sys
class SkippingTestCases(unittest.TestCase):
# 使用 @unittest.skip 强制跳过整个测试方法
@unittest.skip("这个测试方法:test_skip_by_decoration 被强制跳过了")
def test_skip_by_decoration(self):
self.fail("这个测试方法被跳过了")
# 使用条件判断在测试方法内部跳过
def test_skip_in_method_by_condition(self):
if sys.version_info < (3, 6):
self.skipTest("这个测试需要Python 3.6或更高版本")
self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")
# 使用 @unittest.skipIf 根据条件在装饰时跳过
@unittest.skipIf(sys.version_info < (3, 6), "这个测试需要Python 3.6或更高版本")
def test_skip_if_condition_met(self):
self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")
# 使用 @unittest.skipUnless 根据条件在装饰时跳过,当条件不满足时跳过
@unittest.skipUnless(False, "这个测试方法:test_skip_unless_condition_not_met 被故意设置为总是跳过")
def test_skip_unless_condition_not_met(self):
self.fail("这个测试不会执行,因为它被跳过了")
# 使用 @unittest.expectedFailure 标记预期会失败的测试
@unittest.expectedFailure
def test_expected_to_fail(self):
self.assertTrue(True, "这个测试预期会失败,因为它尚未完成或存在已知问题")
if __name__ == '__main__':
unittest.main()
执行结果
BeautifulReport - 运行器扩展
安装
BeautifulReport 属于第三方库,需要额外下载安装,命令如下:
pip install BeautifulReport
示例代码
import unittest
from BeautifulReport import BeautifulReport
# 定义一个测试类
class MathTests(unittest.TestCase):
# 测试加法
def test_addition(self):
self.assertEqual(1 + 1, 3)
# 测试减法
def test_subtraction(self):
self.assertEqual(2 - 1, 1)
# 测试乘法
def test_multiplication(self):
self.assertEqual(2 * 2, 5)
# 测试除法
def test_division(self):
self.assertEqual(4 / 2, 2)
# 测试除法,预期会失败
def test_division_by_zero(self):
with self.assertRaises(ZeroDivisionError):
1 / 0
# 定义另一个测试类
class StringTests(unittest.TestCase):
# 测试字符串连接
def test_string_concatenation(self):
self.assertEqual("Hello, " + "World", "Hello, World")
# 测试字符串相等
def test_string_equality(self):
self.assertEqual("Hello", "Hello")
# 测试字符串不相等
def test_string_inequality(self):
self.assertNotEqual("Hello", "Goodbye")
# 如果这个脚本被直接运行,则执行测试
if __name__ == '__main__':
# 创建测试套件,添加测试类
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MathTests))
suite.addTest(unittest.makeSuite(StringTests))
# 利用BeautifulReport生成测试报告
result = BeautifulReport(suite)
result.report(filename='这是测试报告的文件名称',
description='这是测试报告的描述',
log_path="./test_reports/log.log",
report_dir="./test_reports")
执行结果
unittestreport - 运行器扩展
安装
unittestreport 属于第三方库,需要额外下载安装,命令如下:
pip install unittestreport
示例代码
import unittest
from unittestreport import TestRunner
# 定义一个测试类
class MathTests(unittest.TestCase):
# 测试加法
def test_addition(self):
self.assertEqual(1 + 1, 3)
# 测试减法
def test_subtraction(self):
self.assertEqual(2 - 1, 1)
# 测试乘法
def test_multiplication(self):
self.assertEqual(2 * 2, 5)
# 测试除法
def test_division(self):
self.assertEqual(4 / 2, 2)
# 测试除法,预期会失败
def test_division_by_zero(self):
with self.assertRaises(ZeroDivisionError):
1 / 0
# 定义另一个测试类
class StringTests(unittest.TestCase):
# 测试字符串连接
def test_string_concatenation(self):
self.assertEqual("Hello, " + "World", "Hello, World")
# 测试字符串相等
def test_string_equality(self):
self.assertEqual("Hello", "Hello")
# 测试字符串不相等
def test_string_inequality(self):
self.assertNotEqual("Hello", "Goodbye")
# 如果这个脚本被直接运行,则执行测试
if __name__ == '__main__':
# 创建测试套件,添加测试类
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MathTests))
suite.addTest(unittest.makeSuite(StringTests))
# 利用unittestreport生成测试报告
runner = TestRunner(suite,
filename='report.html',
report_dir="./test_reports",
title='加减乘除和字符串案例的测试报告',
tester="我是测试开发的烦恼",
desc='测试加减乘除、字符串等案例')
runner.run()
执行结果
DDT - 参数化扩展
什么是DDT(数据驱动)
- DDT又叫数据驱动(Data-Driven Design)
- 通常用于将测试数据与函数代码进行分离,存储某个位置的文件中;在自动化测试中数据驱动框架会读取文件中的数据,然后把数据作为参数传递到功能函数中,并会根据数据的条目数量多次运行同一个功能函数;
- 这样做的好处就是提高代码的复用性、可维护性;
- 数据驱动的数据文件可以是:txt、csv、Excel、json、yaml等文件
为什么要使用数据驱动
数据与代码分离:
- 通过将测试数据与函数代码分离,可以使测试代码更加清晰和易于维护。
- 测试数据通常存储在外部文件中,如txt、csv、Excel、json、yaml等,这样可以在不修改代码的情况下更改测试数据。
提高代码复用性:
- 使用数据驱动框架,可以多次运行同一个功能函数,每次使用不同的测试数据。> - 这样,相同的测试逻辑可以被重复使用,提高了代码的复用性。
提高可维护性:
- 当测试数据发生变化时,只需要更新存储测试数据的文件,而不需要修改测试代码。
- 这降低了维护成本,并提高了测试脚本的可维护性。
灵活性:
- 数据驱动测试允许测试人员快速添加、删除或修改测试数据,以适应不同的测试场景和需求。
易于扩展:
- 由于测试数据与代码是分离的,因此可以更容易地扩展测试范围,包括增加新的测试数据或添加新的测试场景。
安装
DDT 属于第三方库,需要额外下载安装,命令如下:
pip install ddt
示例代码
传递一个参数
# 导入unittest框架、ddt驱动模块、data数据传递模块
import unittest
from ddt import ddt
from ddt import data
# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
# 使用@data装饰器将声明的数据传递给测试用例方法中的形参
@data('18900001234','15800001234','13900001234')
# phone(1个形参):接收data传递来的数据
# 因为只有一个形参传递进来的数据都是给phone使用的
def test_01_phone(self,phone):
print('手机号:',phone)
传递多个参数
# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack
# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
# 使用@data装饰器将声明的数据传递给测试用例方法中的形参
@data(['admin','123456','78oa'],['guest','111111','a3b6'])
# username,password,code(3个形参):接收data传递来的数据
# 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
# 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
# 使用@unpack将传递进来的参数进行拆解,分别传递形参
@unpack
def test_02_login(self,username,password,code):
print('账号:',username,'密码:',password,'验证码:',code)
使用ddt读取txt文件内容传递
# phone.txt内容
----------------------------------
15800001111
15600002222
18900003333
13900004444
18011115555
13500006666
# userdata.txt内容
----------------------------------
admin,123456,s7c8
guest,111111,08hg
visitor,000000,kl0s
# ddt_demo.py内容
----------------------------------
# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack
"""
with 语句实质是上下文管理。
1、上下文管理协议。包含方法__enter__() 和 __exit__(),支持该协议对象要实现这两个方法。
2、上下文管理器,定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。
3、进入上下文的时候执行__enter__方法,如果设置as var语句,var变量接受__enter__()方法返回值。
4、如果运行时发生了异常,就退出上下文管理器。调用管理器__exit__方法。
"""
'''
读取phone.txt文件获取数据作为参数
1、声明phone列表用来存储读取并处理后的数据
2、使用with机制以utf-8编码格式用只读的模式open文件
3、循环遍历文件中的所有行,将每一行去除空白符添加到列表中
4、返回phone列表
'''
def read_phone():
phone = []
with open(file='phone.txt',mode='r',encoding='utf-8') as f:
for lina in f.readlines():
phone.append(lina.strip())
return phone
'''
读取userdata.txt文件获取数据作为参数
1、声明user_info列表用来存储读取并处理后的数据
2、使用with机制以utf-8编码格式用只读的模式open文件
3、循环遍历文件中的所有行,将每一行去除空白符后以','分割成列表
4、将处理后的行添加到user_info列表并返回
'''
def read_userdata():
user_info = []
with open(file='userdata.txt',mode='r',encoding='utf-8') as u:
for rows in u.readlines():
rows_str = str(rows.strip()).rsplit(',')
user_info.append(rows_str)
return user_info
# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
# 使用data装饰器,调用read_phone()函数用其返回值传递给测试用例的形参
# 因为文件中含有多条数据,需要使用动态参数传递
# 否则就会被看成一条数据,只会执行一条用例
@data(*read_phone())
# phone(1个形参):接收data传递来的数据
def test_01_phone(self,phone):
print('手机号:',phone)
# 使用data装饰器,调用read_userdata()函数用其返回值传递给测试用例的形参
# 因为文件中含有多条数据,需要使用动态参数传递
# 否则就会被看成一条数据,只会执行一条用例
@data(*read_userdata())
# username,password,code(3个形参):接收data传递来的数据
# 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
# 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
# 使用@unpack将传递进来的参数进行拆解,分别传递形参
@unpack
def test_02_login(self,username,password,code):
print('账号:',username,'密码:',password,'验证码:',code)
if __name__ == '__main__':
unittest.main()
使用ddt读取json数据文件内容传递
# phone.txt内容
----------------------------------
[
"15800001111",
"15600002222",
"18900003333"
]
# userdata.txt内容
----------------------------------
[
{
"username":"admin",
"password":"123456",
"code":"s7c8"
},
["guest","111111","08hg"]
]
# ddt_demo.py内容
----------------------------------
# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块、json模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack
import json
'''
使用json类对象调用load加载函数从phone.json、userdata.json数据文件加载数据
'''
def read_phone():
return json.load(open(file='phone.json',mode='r',encoding='utf-8'))
def read_userdata():
return json.load(open(file='userdata.json',mode='r',encoding='utf-8'))
# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
# 使用data装饰器,调用read_phone()函数用其返回值传递给测试用例的形参
# 因为文件中含有多条数据,需要使用动态参数传递
# 否则就会被看成一条数据,只会执行一条用例
@data(*read_phone())
# phone(1个形参):接收data传递来的数据
def test_01_phone(self,phone):
print('手机号:',phone)
# 使用data装饰器,调用read_userdata()函数用其返回值传递给测试用例的形参
# 因为文件中含有多条数据,需要使用动态参数传递
# 否则就会被看成一条数据,只会执行一条用例
@data(*read_userdata())
# username,password,code(3个形参):接收data传递来的数据
# 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
# 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
# 使用@unpack将传递进来的参数进行拆解,分别传递形参
@unpack
def test_02_login(self,username,password,code):
print('账号:',username,'密码:',password,'验证码:',code)
if __name__ == '__main__':
unittest.main()
parameterized - 参数化扩展
parameterized
是python的第三方库,用于为unittest
测试提供参数化支持。它允许为测试用例提供多个参数集,以便多次运行相同的测试逻辑,但每次使用不同的输入参数。
安装
parameterized 属于第三方库,需要额外下载安装,命令如下:
pip install parameterized
示例代码
from parameterized import parameterized
import unittest
class TestParameterized(unittest.TestCase):
# 单个参数
@parameterized.expand([
("foo",),
("bar",),
])
def test_with_single_parameter(self, s):
self.assertIn(s, ["foo", "bar", "baz"])
# 多个参数
@parameterized.expand([
(1, 2, 3),
(4, 5, 9),
])
def test_with_multiple_parameters(self, a, b, expected_sum):
self.assertEqual(a + b, expected_sum)
# 列表参数
@parameterized.expand([
([1, 2, 3],),
([4, 5, 6],),
])
def test_with_list_parameter(self, lst):
self.assertEqual(sum(lst), sum([i for i in lst]))
# 字典参数
@parameterized.expand([
({"a": 1, "b": 2, "expected": 3},),
({"a": -1, "b": 1, "expected": 0},),
])
def test_with_dict_parameter(self, kwargs):
self.assertEqual(kwargs["a"] + kwargs["b"], kwargs["expected"])
if __name__ == '__main__':
unittest.main()