unittest(一)---基础
一、unittest
unittest是python自带的单元测试框架,基于java的junit测试框架设计。
unittest的重要组件:testCase测试用例、testSuite测试套件、testFixture测试固件(夹具)、testLoader测试加载器、testRunner测试运行器。
unittest单元测试框架的功能:
- 测试发现:从多个py文件中发现并且加载测试用例
- 测试执行:将测试用例按照一定的顺序和条件执行并生成结果
- 测试判断:用断言去判断结果是否正确
- 测试报告:生成测试报告,展示测试结果、通过率等
二、testCase测试用例
1.测试用例及执行
测试用例类必须继承基类unittest.TestCase,测试用例方法必须以test开头
import unittest class testRegister(unittest.TestCase): def test01_user(self): print("username") def test02_psw(self): print("password") if __name__ == "__main__": unittest.main()
运行结果
unittest.main()会执行所有test开头的方法(测试用例),如果方法不以test开头将不会被主动执行。
测试用例的执行不是以代码顺序执行,而是以方法名称的ASCII码顺序执行。
2.执行结果
执行结果:.成功,F失败,E错误,s跳过
import unittest class testRegister(unittest.TestCase):
#成功 def test01_user(self): print("username")
#失败 def test02_psw(self): print("password") self.assertTrue(0)
#错误 def test04_register(self): print("register") raise Exception("自定义异常")
#跳过 @unittest.skip("跳过此用例") def test03_psw_again(self): print("password again") if __name__ == "__main__": unittest.main()
3.框架的执行逻辑
- module='__main__‘ :测试用例所在的位置,__main__指当前模块
- defaultTest=None :待测测试用例的名称,默认是所有;可指定执行部分测试用例
- argv=None :接收外部的参数
- testRunner :测试运行器,默认使用TextTestRunner
- testLoader=loader.defaultTestLoader :测试用例加载器
- exit=True :执行完测试用例后是否退出Python程序;默认退出
- verbosity=1 :输出测试结果的详细程度,有0、1、2,越来越详细;默认为1
if __name__ == "__main__":
#设置执行测试用例和输出结果的详细程度 unittest.main(defaultTest=["testRegister.test01_user","testRegister.test02_psw"],verbosity=2)
4.命令行方式运行
python -m unittest fileName.className.functionNane -v 执行某文件的某类的某个测试用例,也可以写多个文件,用空格隔开
- python -m 将库中的模块用脚本的方式执行
- -v 详细的输出测试结果
python -m unittest discover -s dirName -p "*.py" 执行dirName文件夹下的py文件;文件名必须包含test(大小写皆可)
python -m unittest -h 查看更多参数
三、testSuite测试套件
将测试用例加入到测试套件里,可以选择执行部分测试用例;执行顺序按照加入测试条件的顺序执行
1.测试套件的构建方法
1.1 单个添加测试用例
import unittest class testRegister(unittest.TestCase): def test01_user(self): print("username") def test02_psw(self): print("password") def test04_register(self): print("register") def test03_psw_again(self): print("password again") if __name__ == "__main__": #创建一个测试套件 suite = unittest.TestSuite() #测试套件增加测试用例 suite.addTest(testRegister("test02_psw")) suite.addTest(testRegister("test01_user")) #指定执行套件里的测试用例 unittest.main(defaultTest="suite")
1.2 多个添加测试用例
if __name__ == "__main__": suite = unittest.TestSuite() cases = [testRegister("test02_psw"),testRegister("test01_user")] #一次添加多个测试用例 suite.addTests(cases) #以TextTestRunner()的方式运行 unittest.TextTestRunner(verbosity=2).run(suite)
2.测试加载器构建
import unittest import os if __name__ == "__main__": #使用测试加载器加载测试用例 #也可写作unittest.TestLoader().discover() #discover(self: TestLoader, start_dir: str, pattern: str, top_level_dir: str) -> TestSuite #加载当前目录下,test开头的py文件的全部测试用例 suite = unittest.defaultTestLoader.discover(start_dir=os.getcwd(), pattern="test*.py") unittest.main(defaultTest="suite")
3.跳过测试用例
- @unittest.skip(reason):无条件跳过测试用例,并说明原因(如果verbosity=2会打印出原因,如果是1只会打印s);放在类前面就是跳过类下面的所有测试用例,放在单个测试用例前面,就是跳过此测试用例
- @unittest.skipIf(condition,reason):如果条件为真时就跳过测试用例
- @unittest.skipUnless(condition,reason):如果条件为假时就跳过测试用例
- @unittest.expectedFailure:预期该测试用例结果为失败,如果测试用例失败则不记为失败,记为expected failure(如果verbosity=1,记为x),如果测试用例成功则记为unexpected success(如果verbosity=1,记为u)
import unittest class testRegister(unittest.TestCase): @unittest.skip("跳过此用例") def test01_user(self): print("username") @unittest.skipIf(1<2,"如果1小于2就跳过此用例") def test02_psw(self): print("password") @unittest.skipUnless(1>2,"如果1不大于2就跳过此用例") def test03_psw_again(self): print("password again") @unittest.skipIf(1 != 2, "如果1不等于2就跳过类里面的所有用例" ) class testRegister2(unittest.TestCase): def test01_user2(self): print("username") def test02_psw2(self): print("password") if __name__ == "__main__": unittest.main(verbosity=2)
import unittest class testRegister(unittest.TestCase): @unittest.expectedFailure def test01_user(self): print("username") self.assertTrue(0) #失败 @unittest.expectedFailure def test02_psw(self): print("password") self.assertTrue(1) #成功 if __name__ == "__main__": unittest.main(verbosity=2)
四、testFixture测试固件
- setUp/tearDown:在每个测试用例之前/之后运行一遍;主要用于打开浏览器,加载网页等/关闭网页
- setUpClass/tearDownClass:在每个类之前/之后运行一遍;主要用于创建数据库连接、创建日志对象/关闭数据库连接、销毁日志对象
- setUpModule/tearDownModule:在每个模块之前/之后运行一边
import unittest class testRegister(unittest.TestCase): @classmethod def setUpClass(cls): print("在类的所有用例开始前运行") @classmethod def tearDownClass(cls): print("在类的所有用例结束后运行") def setUp(self): print("在每个测试用例之前运行") def tearDown(self): print("在每个测试用例之后运行") def test01_user(self): print("username") def test02_psw(self): print("password") if __name__ == "__main__": unittest.main()
import unittest def setUpModule(): print("在模块开始之前运行") def tearDownModule(): print("在模块结束之后运行") class testRegister(unittest.TestCase): @classmethod def setUpClass(cls): print("在类的所有用例开始前运行") @classmethod def tearDownClass(cls): print("在类的所有用例结束后运行") def test01_user(self): print("username") def test02_psw(self): print("password") class testRegister2(unittest.TestCase): def test01_user2(self): print("username") def test02_psw2(self): print("password") if __name__ == "__main__": unittest.main()
五、断言
断言用于校验实际结果与预期结果是否匹配;(断言内容必须选择核心内容,需要能明确表示是否成功的因素;比如用title判断是否跳转到网页,可能title已经是了,但是因为网页问题内容并没有加载出来)
1.常用断言
方法 | 检查 |
assertEqual(a,b) | a == b |
assertNotEqual(a,b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(c) | bool(x) is False |
assertIn(a,b) | a in b |
assertNotIn(a,b) | a not in b |
assertIs(a,b) | a is b |
assertIsNot(a,b) | a is not b |
assertIsNone(x) | x is none |
assertIsNotNone(x) | x is not none |
2.简单例子
import unittest class testRegister(unittest.TestCase): def test01_user(self): self.assertEqual("username","username") def test02_pws(self): self.assertIn("pws","password") if __name__ == "__main__": unittest.main()
六、HTMLTestRunner
HTMLTestRunner是unittest的一个第三方库,可以生成简单明了的html格式的报告。本文使用的HTMLTestRunner来自https://github.com/Gelomen/HTMLTestReportCN-ScreenSho,下载后将py文件改名为HTMLTestRunner.py,并放在python\Lib\site-packages目录下。
import unittest import HTMLTestRunner import os import time class testRegister(unittest.TestCase): def test01_user(self): """这个用例会成功""" self.assertEqual("username","username") def test02_pws(self): """这个用例会失败""" self.assertIn("pws","password") if __name__ == "__main__": suite = unittest.TestLoader().discover(start_dir="./",pattern="test*") nowtime = time.strftime("%Y-%m-%d %H-%M-%S", time.localtime()) fp = open(os.getcwd()+"\\"+nowtime+"report.html","wb") runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title="测试报告",description="自动化测试报告",tester="测试") runner.run(suite) fp.close()
七、ddt
ddt(data-driven test)数据驱动测试,可以实现不同数据运行同一个测试用例。数据和测试用例分离,避免编写重复测试脚本。通过数据驱动,来验证多组数据测试场景。
使用ddt需先安装,pip install ddt
ddt模块包含一个类装饰器ddt和3个方法装饰器
- ddt:装饰继承TestCase的类
- data:装饰方法,参数是一系列的值,可以是字符串数字等,也可以是列表元组字典等;一次只传递一个参数,列表元组字典会当作一个参数传递,在前面添加*会拆分成多组传递
- file_data:装饰方法,参数是文件;会从yaml或json中加载数据;如果是.yml或.yaml文件以yaml格式加载数据,其他文件都以json格式加载数据
- unpack:如果传递的是复杂的数据结构时,如列表元组字典,添加unpack会把列表元组字典拆分成多个参数传递
1.data
import unittest from ddt import ddt,data,unpack @ddt class testDdt(unittest.TestCase): @data(1,2,3,[4,5]) def test_ddt(self,a): #只能有一个参数 print(a) if __name__ == "__main__": unittest.main(verbosity=2)
#如果参数是一个数组 import unittest from ddt import ddt,data,unpack @ddt class testDdt(unittest.TestCase): @data([1,2,3,4]) def test_ddt(self,a): #只能有一个参数 print(a) if __name__ == "__main__": unittest.main(verbosity=2)
#如果列表前面那加了*;如果是字典前面加了*,打印出来的a的值是字典的key import unittest from ddt import ddt,data,unpack @ddt class testDdt(unittest.TestCase): @data(*[1,2,3,4]) def test_ddt(self,a): #只能有一个参数 print(a) if __name__ == "__main__": unittest.main(verbosity=2)
2.unpack
#unpack import unittest from ddt import ddt,data,unpack @ddt class testDdt(unittest.TestCase): @data([1,2],[3,4]) @unpack def test_ddt(self,a,b): #2个参数,如果传入的data是字典,参数名称必须与data的key相同 print(a,b) if __name__ == "__main__": unittest.main(verbosity=2)
3.ddt处理yaml格式
首先要安装pip install PyYAML,用于处理yaml文件;关于yaml文件格式基础知识:https://www.runoob.com/w3cnote/yaml-intro.html
import unittest from ddt import ddt,file_data @ddt class testDdt(unittest.TestCase): @file_data("testdata.yaml") def test_ddt(self,**a): print(a) print(type(a)) #字典格式 print(a.get("id")) #@file_data("testdata.yaml") #def test_ddt(self,id,name,price): #也可以用字典的key来接受数据 #print(id,name,price) #test_ddt_00001 ... 001 icecream 100 if __name__ == "__main__": unittest.main(verbosity=2)