目录
8.2、defaultTestLoader通过类名、模块名加载
1、什么是UnitTest框架
概念:UnitTest是Python自带的一个单元测试框架,可以用它来做单元测试
2、为什么使用UnitTest框架
-
能够组织多个用例去执行
-
提供丰富的断言方法
-
能够生成测试报告
3、UnitTest核心要素
-
TestCase(测试用例)
-
TestSuite(测试套件,把多个TestCase集成到一个测试TestSuite)
-
TextTestRunner(执行测试用例)
-
Fixture(测试夹具)
4、UnitTest使用流程
第一步:导入unittest模块
第二步:创建一个类,这个类必须继承自UnitTest.TestCase类
第三步:类中每个方法代表一个测试用例,方法名必须以test开头
第四步:将测试用例加入到TestSuite
第五步:使用TextTestRunner执行
5、unittest断言
让程序代替人判断程序执行结果是否符合预期结果的过程,需要在测试用例中对测试结果进行断言。
assertEqual(a, b, msg=None) | # a == b 断言a和b是否相等,相等则测试用例通过 |
assertNotEqual(a, b,msg=None) | # a != b 断言a和b是否相等,不相等则测试用例通过 |
assertTrue(x,msg=None) | # x is True 断言x是否True,是True则测试用例通过 |
assertFalse(x,msg=None) | # x is False 断言x是否False,是False则测试用例通过 |
assertIn(a, b,msg=None) | # a in b 断言a是否在b中,在b中则测试用例通过 |
assertNotIn(a, b,msg=None) | # a not in b 断言a是否在b中,不在b中则测试用例通过 |
-
assertEqual(参数1,参数2,msg=None)
-
如果参数1和参数2相等,断言成功,否则断言失败
-
参数1存放实际结果,参数2存放预期结果
-
msg默认信息为None,可以给msg指定信息,当断言失败时,会作为错误信息返回
-
6、TestCase测试用例
在编写测试用例之前,我们需要创建一个存放测试用例的类,导入unittest,并且要继承unittest.TestCase类,该类中的方法必须要以test开头
下面创建两个测试类,StringTest和NumberTest,分别写了两个测试方法
StringTest类
import unittest
class StringTest(unittest.TestCase):
def test_Method1(self):
self.assertIn("hello","hello world")
def test_Method2(self):
self.assertEqual("H","Happy")
NumberTest类
import unittest
class NumberTest(unittest.TestCase):
def test_funa(self):
self.assertEqual(1,2,"两个参数不相等")
def test_funb(self):
self.assertEqual(2,2)
这两个类可以直接运行,直接在该类中,右键点击Run Unittests for 模块名.类名,就可以直接查看测试结果,或者也可以加入如下代码,直接调用unittest.main()来执行
if __name__ == "__main__":
unittest.main()
单独执行NumberTest类,一共两条测试用例,一条通过,一条不通过,并打印了错误信息
7、TestSuit测试套件
上面介绍了如何执行单个测试类里面的测试用例,但是测试是不可能只写一个测试类,当需要执行多个测试类中的测试方法时,需要用到测试套件,把测试用例组装起来。
1、实例化:suite = unittest.TestSuit() (suite:为TestSuit实例化的名称)
2、添加单个用例:
suit.addTest(ClassName("MethodName")) (ClassName:为类名 MethodName:为方法名)
3、添加多个测试用例,参数为列表suit.addTests([ClassName("MethodName1"),ClassName("MethodName2")])
4、TestSuit需要配合TextTestRunner才能被执行,
还以StringTest和NumberTest为例,介绍TestSuit的使用
#导入测试类
from frame_unittest.numberTest import NumberTest
from frame_unittest.stringTest import StringTest
import unittest
if __name__ == "__main__":
suit = unittest.TestSuite() #实例化测试套件
suit.addTest(NumberTest("test_funa")) #单个添加测试用例
suit.addTest(NumberTest("test_funb"))
suit.addTests([StringTest("test_Method1"),StringTest("test_Method2")]) #添加多个测试用例
runner = unittest.TextTestRunner() #实例化TextTestRunner
runner.run(suit) #执行测试用例
8、TestLoader测试加载
8.1、discover加载
使用unnitest.TestLoader,通过该类下面的discover()方法自动搜索指定开头的.py文件,并查找到的测试用例组装到测试套件
用法:suit = unittest.TestLoader().discover(test_dir,pattern='test*.py')
import unittest
import os
if __name__ == "__main__":
path = os.path.dirname(__file__) #获取当前文件所在目录的路径
suit = unittest.TestLoader().discover(path,"*Test.py") #加载当前目录下,以Test.py结尾的模块
runner = unittest.TextTestRunner() #实例化TextTestRunner
runner.run(suit) #执行测试用例
8.2、defaultTestLoader通过类名、模块名加载
defaultTestLoader可以通过类名和模块名的方式加载,与TestLoader中discover不同的点在于,defaultTestLoader还需要把加载的内容添加到TestSuit中,discover则不用。执行都需要使用TextTestRunner去执行
下面的测试用例还是借用上述的StringTest和NumberTest,使用类名和模块名两种当时执行测试用例
unittest.defaultTestLoader.loadTestsFromTestCase(类名) 添加一个类
import unittest
from frame_unittest.numberTest import NumberTest
from frame_unittest.stringTest import StringTest
#加载的是类名
nt =unittest.defaultTestLoader.loadTestsFromTestCase(StringTest)
st =unittest.defaultTestLoader.loadTestsFromTestCase(NumberTest)
suit = unittest.TestSuite([st,nt])
unittest.TextTestRunner().run(suit)
unittest.defaultTestLoader.loadTestsFromModule(模块名) 添加一个模块
import unittest
#导入需要加载的模块
from frame_unittest import numberTest
from frame_unittest import stringTest
names =[numberTest,stringTest]
modules=[]
#加载模块名
for name in names:
module=unittest.defaultTestLoader.loadTestsFromModule(name)
modules.append(module)
suit = unittest.TestSuite(modules)
unittest.TextTestRunner().run(suit)
8.3、小结
组装测试套件有多种方式
方式一:添加单个测试类的单个测试方法 组装到测试套件中 addTest()
方式二:搜索该路径下所有符合命名规则的模块组装到测试套件中 discover()
方式三:通过加载类名的方式组装到测试套件 loadTestsFromTestCase()
方式四:用过加载模块名的方式组装到测试套件 loadTestsFromModule()
-
所有的TestCase最终都是用TextTestRunner来执行的
-
TextTestRunner执行的是TestSuit
-
一个TestSuit中有多个TestCase
9、Fixture测试夹具
Fixture是一个概述,对一个测试用例环境的初始化和销毁就是一个Fixture
可以在测试用例执行之前调用指定的函数,在测试用例执行之后调动指定的函数
Fixture控制级别
-
方法级别
-
每个方法执行前和执行后都自动调用函数
-
-
类级别
-
不管类中有多少方法,一个类执行前后都自动调用函数
-
-
模块级别
-
不管一个模块(一个模块就是一个py文件)中有多少类,模块执行前后自动调用函数
-
1、方法级
在TestCase,也就是测试用例所在的class中定义方法
def setUp(self)当测试用例执行前,自动被调用
def tearDown(self)当测试用例执行后,自动被调用
如果一个TestCase中有多个测试用例,那么setUp和tearDown就会被自动调用多次
import unittest
def add(a,b):
sum = a + b
return sum
class my_test(unittest.TestCase):
def setUp(self):
print("setup被调用了")
def tearDown(self):
print("teardown被调用了")
def test_001(self):
print(add(4,5))
def test_002(self):
print(add(0,6))
def test_003(self):
print(add(2,4))
2、类级别:
-
不管类中有多少个方法,一个类开始的时候自动调用函数,结束之后自动调用函数
-
类级别的fixture一定要有类方法@classmethod
import unittest
def add(a,b):
return a+b
class my_test_demo(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("这个是类级别测试开始")
@classmethod
def tearDownClass(cls):
print("这个是类级别的结束")
def test_01(self):
print(add(3,2))
def test_02(self):
print(add(1,2))
3、模块级别:
-
不管py文件有多少个类,以及类中有多少个方法,只自动执行一次
-
def setUpModule() 在py文件开始的时候自动调用
-
def tearDownModule()在文件结束的时候自动调用
import unittest
def add(a,b):
return a+b
#模块级别
def setUpModule():
print("setUpModule自动调用了")
#模块级别
def tearDownModule():
print("tearDownModule调用结束了")
class my_test_demo(unittest.TestCase):
#类级别
@classmethod
def setUpClass(cls):
print("这个是类级别测试开始")
@classmethod
def tearDownClass(cls):
print("这个是类级别的结束")
def test_01(self):
print(add(3,2))
def test_02(self):
print(add(1,2))
#方法级别
def setUp(self):
print("setup被调用了")
def tearDown(self):
print("teardown被调用了")
10、参数化
测试用例中使用参数化的场景:多个测试用例代码相同,只是测试数据不同,预期结果不同,可以把多个测试用例通过参数化技术合并为一个
10.1、环境准备
因为参数化的插件 不是 unittest 自带的,所以想要使用 需要进行安装
10 .2、通过元组列表实现参数化
定义一个加法函数,模块名为tools
def add(a,b):
return a+b
定义一个测试类,模块名为TestAdd.py
from parameterized import parameterized #导入parameterized
import unittest
from frame_unittest.tools import add
#使用data存放一组测试数据
data = [(1, 1, 2), (1, 2, 3), (2, 3, 5), (4, 5, 9)]
class Test_Add(unittest.TestCase):
@parameterized.expand(data) #data数据与测试用例的参数一一对应
def test(self,a,b,expect):
self.assertEqual(add(a,b),expect)
if __name__ == "__main__":
unittest.main()
10.3、使用json数据参数化
JSON格式一
定义一个data.json
[
[1, 1, 2],
[1, 2, 3],
[2, 3, 5],
[4, 5, 9],
[10, 20, 30]
]
定义一个TestAdd_Method.py
import unittest
import os
import json
from parameterized import parameterized
from frame_unittest.tools import add
#获取当前文件所在目录
path = os.path.abspath(os.path.dirname(os.getcwd()))
#定义一个读取json数据的方法
def read_data():
with open(path+"/data/add.json",mode="r",encoding="utf-8") as file:
data = json.load(file)
return data
class TestMethod(unittest.TestCase):
@parameterized.expand(read_data())
def test_method(self,a,b,expect):
self.assertEqual(add(a,b),expect)
JSON格式二
定义一个data1.json
[
{ "a": 1, "b": 2, "expect": 3 },
{ "a": 11, "b": 22, "expect": 33 },
{ "a": 12, "b": 23, "expect": 35 },
{ "a": 14, "b": 25, "expect": 39 }
]
建立一个模块名为addmethod.py的测试类
import os
import unittest
import json
from frame_unittest.tools import add
from parameterized import parameterized
def readdata():
path = os.path.abspath(os.path.dirname(os.getcwd()))
with open(path+"/data/add1.json",mode="r",encoding="utf-8") as file:
datas = json.load(file) #读取json文件
list_data = []
for data in datas:
a = data.get("a")
b = data.get("b")
expect = data.get("expect")
list_data.append((a,b,expect))
return list_data #返回[(),(),()...]的数据格式
#list_data.append(tuple(data.values()))
class AddMethod(unittest.TestCase):
@parameterized.expand(readdata())
def test_add(self,a,b,expect):
self.assertEqual(add(a,b),expect)
11、跳过
import unittest
version = 35
class TestSkip(unittest.TestCase):
@unittest.skip('跳过此条不执行')
def test_1(self):
print('方法一')
@unittest.skipIf(version >= 30, '版本号大于等于 30, 测方法不用执行')
def test_2(self):
print('方法二')
def test_3(self):
print('方法三')
if __name__ == '__main__':
unittest.main()
执行结果
============================= test session starts =============================
collecting ... collected 3 items
mm.py::TestSkip::test_1 SKIPPED (跳过此条不执行) [ 33%]
Skipped: 跳过此条不执行
mm.py::TestSkip::test_2 SKIPPED (版本号大于等于 30, 测方法不用执行) [ 66%]
Skipped: 版本号大于等于 30, 测方法不用执行
mm.py::TestSkip::test_3 PASSED [100%]方法三
======================== 1 passed, 2 skipped in 0.02s =========================
12、测试报告
使用第三方的报告模版,生成报告 HTMLTestReport, 本质是 TestRunner
- 安装
pip install -i https://pypi.douban.com/simple/ HTMLTestReport
- 使用
-
导包 unittest、HTMLTestReport
-
组装用例(套件, loader )
-
使用 HTMLTestReport 中的 runner 执行套件
-
查看报告
以最开始的stringtest和numbertest为例
import unittest
from htmltestreport import HTMLTestReport
import os
if __name__ == "__main__":
path = os.path.dirname(__file__)
suit = unittest.TestLoader().discover(path+"/","*test.py")
# runner = HTMLTestReport(报告的文件路径后缀.html, 报告的标题, 其他的描述信息)
runner = HTMLTestReport("report.html","数据类型测试报告")
runner.run(suit)
生成的测试报告如下