unittest使用指南
unittest是Python标准库中用于编写单元测试的模块。它提供了一系列用于编写和运行测试的工具和API,包括测试用例、测试套件、测试运行器和断言方法等。
unittest中的基本组件包括:
TestCase:测试用例是一个包含一个或多个测试方法的类。测试方法应该以test_为前缀,以便unittest能够自动发现并运行它们。
TestSuite:测试套件是一组测试用例的集合。它可以包含其他测试套件,这样可以方便地组织和运行一组相关的测试。
TestRunner:测试运行器是用于运行测试套件的对象。unittest中提供了TextTestRunner、HTMLTestRunner等多种测试运行器。
Assertions:断言方法是用于验证测试结果的方法,例如assertEqual()、assertTrue()、assertFalse()、assertRaises()等。
使用unittest编写单元测试通常需要继承unittest.TestCase类,并实现测试方法。在测试方法中,使用断言方法验证测试结果是否符合预期。然后将测试用例添加到测试套件中,最后通过测试运行器运行测试套件并查看测试结果。
unittest是Python中常用的单元测试框架之一,它提供了丰富的工具和API来支持单元测试的编写和运行。
第一个Unittest程序
编写测试用例前,我们需要建一个测试类继承unittest里面的TestCase类,继承这个类之后我们才是真正的使用unittest框架去写测试用例,编写测试用例的步骤如下:
导入unittest模块
创建一个测试类,并继承
unittest.TestCase()
定义测试方法,方法名必须以test_开头
调用
unittest.main()
方法来运行测试用例,unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行
以下是一个简单的unittest示例程序:
import unittest
# 要测试的函数
def add(x, y):
return x + y
# 继承unittest.TestCase类来编写测试用例
class TestAdd(unittest.TestCase):
def test_add_positive_numbers(self):
# 断言函数的返回值是否等于预期值
self.assertEqual(add(2, 3), 5)
def test_add_negative_numbers(self):
self.assertEqual(add(-2, -3), -5)
def test_add_zero(self):
self.assertEqual(add(0, 0), 0)
# 运行测试用例
if __name__ == '__main__':
unittest.main()
setUp
setUp
是 unittest中的一个特殊函数,在测试函数运行之前被调用。它可以用于为每个测试准备测试环境,例如创建测试数据或初始化系统设置。
unittest的setup分为两种,第一种是函数级别的setup,另外一种是类级别的setup。重写setUP方法即可使用setUp
这个特殊函数
import unittest
class Test002(unittest.TestCase):
def setUp(self):
print("开始")
@classmethod
def setUpClass(cls):
print("类开始")
def test_format(self):
str = "{0},{1}".format("hello", "world")
print(str)
def test_format_str(self):
print("hello world")
# 运行测试用例
if __name__ == '__main__':
unittest.main()
运行之后,test_format
和test_format_str
测试函数会在执行之前都会执行一次setUp
里面的内容,而setUpClass
会在测试类运行之前运行一次里面的内容。
注意setUpClass
一定要加@classmethod
装饰器,否则会报错。
tearDown
tearDown
是unittest中的一个特殊函数,在测试函数运行之后被调用。它可以用于清理测试环境,例如删除测试数据或重置系统设置。
unittest的tearDown分为两种,第一种是函数的tearDown,另外一种是类的tearDown。重写tearDown方法即可使用tearDown
这个特殊函数。
import unittest
class Test002(unittest.TestCase):
def tearDown(self):
print("结束")
@classmethod
def tearDownClass(cls):
print("类结束")
def test_format(self):
str = "{0},{1}".format("hello", "world")
print(str)
def test_format_str(self):
print("hello world")
# 运行测试用例
if __name__ == '__main__':
unittest.main()
运行之后,test_format
和test_format_str
测试函数会在执行之后都会执行一次tearDown
里面的内容,而tearDownClass
会在测试类运行之后运行一次里面的内容。
注意tearDownClass
一定要加@classmethod
装饰器,否则会报错。
TestSuite
在Python的unittest模块中,TestSuite是一个用于组织和运行一组测试用例的类。它可以包含多个TestCase对象或其他TestSuite对象,形成一个层次结构,从而方便地组织和运行测试。
例如,可以创建一个TestSuite对象,将多个相关的TestCase对象添加到其中,然后运行整个测试套件,以便同时运行所有测试。通过将测试用例组织在测试套件中,可以更好地管理和维护测试代码,并且可以使用不同级别的测试套件来运行不同范围的测试,从而更好地控制测试的细粒度程度。
举例:
import unittest
class Test001(unittest.TestCase):
def test_A(self):
print("Test A")
class Test002(unittest.TestCase):
def test_B(self):
print("Test B")
class Test003(unittest.TestCase):
def test_C(self):
print("Test C")
if __name__ == '__main__':
test_suite = unittest.TestSuite()
test_suite.addTest(Test001("test_A"))
test_suite.addTest(Test002("test_B"))
test_suite.addTest(Test003("test_C"))
runner = unittest.TextTestRunner()
runner.run(test_suite)
使用TestSuite
的流程如下:
新建
TestSuite
对象,unittest.TestSuite()
调用
addTest
方法,新建测试对象,并填写测试方法。test_suite.addTest(Test001("test_A"))
新建
TextTestRunner
对象,unittest.TextTestRunner()
将装好用例的
TestSuite
对象放在Runner中运行,runner.run(test_suite)
addTest
适合加载一个一个用例对象,上面例子中有三个用例对象,可以使用addTests
方法一次加载多个用例对象
if __name__ == '__main__':
test_suite = unittest.TestSuite()
tests = [Test001("test_A"),Test002("test_B"),Test003("test_C")]
test_suite.addTests(tests)
runner = unittest.TextTestRunner()
runner.run(test_suite)
若想一次加载完测试用例中所有的测试方法,可以用unittest.TestLoader().loadTestsFromTestCase(testCaseClass=testCaseClass)
装载测试方法,例如:
import unittest
class Test001(unittest.TestCase):
def test_A(self):
print("Test A")
def test_A2(self):
print("Test A2")
def test_A3(self):
print("Test A3")
class Test002(unittest.TestCase):
def test_B(self):
print("Test B")
class Test003(unittest.TestCase):
def test_C(self):
print("Test C")
if __name__ == '__main__':
test_suite = unittest.TestSuite()
tests = [unittest.TestLoader().loadTestsFromTestCase(testCaseClass=Test001),Test002("test_B"),Test003("test_C")]
test_suite.addTests(tests)
runner = unittest.TextTestRunner()
runner.run(test_suite)
TestRunner
TestRunner是Python中unittest模块中的一个类,用于执行测试用例并生成测试报告。unittest模块提供了多种TestRunner的实现,包括TextTestRunner、HTMLTestRunner等。其中,TextTestRunner是最常用的一种TestRunner,它会在控制台中输出测试结果的摘要信息。而HTMLTestRunner则会生成一个HTML格式的测试报告,用于展示测试结果、测试覆盖率等信息。
TestRunner中最重要的方法是run()方法,它会遍历测试套件中所有的测试用例,并执行每个测试用例中的测试方法。run()方法返回一个TestResult对象,它包含了测试结果的详细信息,包括测试用例的执行情况、测试用例中每个测试方法的执行情况、测试用例的通过率等。通常,我们可以将TestResult对象传递给TestRunner的构造函数,以便在测试完成后获取测试结果。
import unittest
class Test001(unittest.TestCase):
def test_A(self):
print("Test A")
if __name__ == '__main__':
test_suite = unittest.TestSuite()
test_suite.addTest(Test001("test_A"))
runner = unittest.TextTestRunner()
runner.run(test_suite)
DDT
"DDT"一词通常指的是"Data-Driven Testing"(数据驱动测试),它可以使用外部数据源来定义和执行测试用例,而不是硬编码测试用例。
安装
pip install ddt
ddt的四大模块
ddt与unittest一起结合使用,其库有四个常用模块,如下:
@ddt
: 引入ddt模块@data
: 导入数据@unpack
: 拆分数据@file_data
: 导入文件数据
@ddt
为了让unittest导入ddt模块,需要在测试类前加@ddt
以标明它为一个数据驱动型测试类
import unittest
from ddt import ddt, data
@ddt
class TestCases(unittest.TestCase):
@data(1, 2, "name")
def test_case_001(self, value):
print(f"test_case_01 {value}")
if __name__ == '__main__':
unittest.main()
运行结果:
============================= test session starts =============================
collecting ... collected 3 items
TestCases.py::TestCases::test_case_001_1_1
TestCases.py::TestCases::test_case_001_2_2
TestCases.py::TestCases::test_case_001_3_name
============================== 3 passed in 0.52s ==============================
Process finished with exit code 0
PASSED [ 33%]test_case_01 1
PASSED [ 66%]test_case_01 2
PASSED [100%]test_case_01 name
@data
@data
装饰器用于指定测试数据。它可以接受不同的参数形式来定义测试数据。在测试方法前添加@data装饰器即可使用。
@data
可以传入多个数据,传入的参数可以是列表、元组、字典,unittest会根据这些数据执行对应的用例,其传入的语法如下:
@data(value01, value02, value03, ...)
直接传入数据
对测试方法中的单个形参传入多个数据进行测试,可以参考上面的例子。对测试方法中的多个形参传入多个数据进行测试,例子如下:
import unittest
from ddt import ddt, data, unpack
@ddt
class TestCases(unittest.TestCase):
@data([1, 2, "name"], [3, 4, "age"], [5, 6, "company"])
@unpack
def test_case_001(self, value01, value02, value03):
print(f"test_case_01 {value01} {value02} {value03}")
if __name__ == '__main__':
unittest.main()
运行结果:
============================= test session starts =============================
collecting ... collected 3 items
TestCases.py::TestCases::test_case_001_1__1__2___name__
TestCases.py::TestCases::test_case_001_2__3__4___age__
TestCases.py::TestCases::test_case_001_3__5__6___company__
============================== 3 passed in 0.23s ==============================
Process finished with exit code 0
PASSED [ 33%]test_case_01 1 2 name
PASSED [ 66%]test_case_01 3 4 age
PASSED [100%]test_case_01 5 6 company
可以看到,测试方法有多个形参的时候,在每个数据用列表将数据装好,并用@unpack
对数据进行解包。如果不用@unpack
,则将列表当作一个数据传入到测试方法里面。
如果重写tearDown
、setUp
,@data
每一次执行都会触发这些类运行,如:
import unittest
from ddt import ddt, data
@ddt
class TestCases(unittest.TestCase):
def tearDown(self):
print("在测试方法运行之后执行")
def setUp(self):
print("在测试方法运行之前执行")
@data(1, 2, "name")
def test_case_001(self, value01):
print(f"test_case_01 {value01}")
if __name__ == '__main__':
unittest.main()
运行结果:
============================= test session starts =============================
collecting ... collected 3 items
TestCases.py::TestCases::test_case_001_1_1
TestCases.py::TestCases::test_case_001_2_2
TestCases.py::TestCases::test_case_001_3_name
============================== 3 passed in 0.16s ==============================
Process finished with exit code 0
PASSED [ 33%]在测试方法运行之前执行
test_case_01 1
在测试方法运行之后执行
PASSED [ 66%]在测试方法运行之前执行
test_case_01 2
在测试方法运行之后执行
PASSED [100%]在测试方法运行之前执行
test_case_01 name
在测试方法运行之后执行
调用函数
@data
不仅仅可以直接传入数据,也可以通过函数将数据传入到@data
中。比如:
import unittest
from ddt import ddt, data
def create_list(count):
return count
@ddt
class TestCases(unittest.TestCase):
@data(create_list(3))
def test_case_001(self, value01):
print(f"test_case_01 {value01}")
if __name__ == '__main__':
unittest.main()
如果标明为多个数据,则函数可以返回一个列表,然后在@data
中加*将列表解包,可以理解为将最外层的[]
去掉。
import unittest
from ddt import ddt, data
def create_list(count):
return [i for i in range(count)]
@ddt
class TestCases(unittest.TestCase):
@data(*create_list(3))
def test_case_001(self, value01):
print(f"test_case_01 {value01}")
if __name__ == '__main__':
unittest.main()
运行结果为:
============================= test session starts =============================
collecting ... collected 3 items
TestCases.py::TestCases::test_case_001_1_0
TestCases.py::TestCases::test_case_001_2_1
TestCases.py::TestCases::test_case_001_3_2
============================== 3 passed in 0.17s ==============================
Process finished with exit code 0
PASSED [ 33%]test_case_01 0
PASSED [ 66%]test_case_01 1
PASSED [100%]test_case_01 2
对于多个形参,写法如下:
import unittest
import random
from ddt import ddt, data, unpack
def create_list(count):
return [random.randint(0, 100) for i in range(count)]
@ddt
class TestCases(unittest.TestCase):
@data(create_list(3), create_list(3), create_list(3))
@unpack
def test_case_001(self, value01, value02, value03):
print(f"test_case_01 {value01} {value02} {value03}")
if __name__ == '__main__':
unittest.main()
运行结果如下:
============================= test session starts =============================
collecting ... collected 3 items
TestCases.py::TestCases::test_case_001_1__11__99__63_
TestCases.py::TestCases::test_case_001_2__24__42__5_
TestCases.py::TestCases::test_case_001_3__40__65__98_
============================== 3 passed in 0.15s ==============================
Process finished with exit code 0
PASSED [ 33%]test_case_01 11 99 63
PASSED [ 66%]test_case_01 24 42 5
PASSED [100%]test_case_01 40 65 98
上面也可以这么写:
import unittest
import random
from ddt import ddt, data, unpack
def create_list(count):
list = []
for i in range(count):
list.append([random.randint(0, 100) for j in range(count)])
return list
@ddt
class TestCases(unittest.TestCase):
@data(*create_list(3))
@unpack
def test_case_001(self, value01, value02, value03):
print(f"test_case_01 {value01} {value02} {value03}")
if __name__ == '__main__':
unittest.main()
BeautifulReport
beautifulreport是Python中生成HTML格式的测试报告的第三方库,beautifulreport 提供了更加灵活的测试报告生成方式,支持自定义模板、主题等功能。
安装
进入github中把源码下载下来,直接放在项目使用即可
下载地址: https://github.com/TesterlifeRaymond/BeautifulReport
案例
import unittest
from beautifulreport import BeautifulReport
class TestMath(unittest.TestCase):
def test_addition(self):
self.assertEqual(1+1, 2)
def test_subtraction(self):
self.assertEqual(3-1, 2)
def test_multiplication(self):
self.assertEqual(2*3, 6)
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestMath))
report_path = 'test_report.html'
result = BeautifulReport(suite)
result.report(filename=report_path, description='Test Report', log_path='.')
在这个例子中,我们导入了unittest和beautifulreport两个模块,并创建了一个测试用例类TestMath。然后,我们创建了一个测试套件suite并将TestMath类中的所有测试方法都添加到了测试套件中。接着,我们指定了测试报告的输出路径report_path。最后,我们使用BeautifulReport类创建了一个result对象,并使用report()方法生成测试报告,其中filename参数指定了输出文件名,description参数指定了测试报告的标题,log_path参数指定了日志文件的输出路径。
执行以上代码后,会在当前目录下生成一个名为test_report.html的文件,用浏览器打开该文件即可查看测试报告。测试报告中包含了测试结果的摘要、测试用例的执行情况、测试用例中每个测试方法的执行情况等详细信息,还可以查看测试覆盖率、测试用时等指标。