1. UnitTest 命令行接口及测试用例自动发现
1.1 UnitTest 命令行接口
unittest 模块可以通过命令行运行模块、类和独立测试方法的测试:
python -m unittest test_module1 test_module2<br /> python -m unittest test_module.TestClass <br /> python -m unittest test_module.TestClass.test_method
这里-m 的作用是 意思是将库中的python模块用作脚本去运行
你可以传入模块名、类或方法名或他们的任意组合。
例子 4-1:
** 文件目录: **
<br /> ** **<br />** Test_A_B.py 代码**
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-6 22:45
import unittest
class Test_A(unittest.TestCase):
def test_caseA1(self):
print("test_case A1 is run !")
pass
def test_caseA2(self):
print("test_case A2 is run !")
pass
def test_caseA3(self):
print("test_case A3 is run !")
pass
class Test_B(unittest.TestCase):
def test_caseB1(self):
print("test_case B1 is run !")
pass
def test_caseB2(self):
print("test_case B2 is run !")
pass
def test_caseB3(self):
print("test_case B3 is run !")
pass
包含Test_A 和Test_B 类 里面各有3个测试用例
Test_C.py 代码
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-6 22:45
import unittest
class Test_C(unittest.TestCase):
def test_caseC1(self):
print("test_case C1 is run !")
pass
def test_caseC2(self):
print("test_case C2 is run !")
pass
def test_caseC3(self):
print("test_case C3 is run !")
pass
Test_C 也包含3个测试用例
因此通过命令行执行测试用例的方法是:
1 . 首先进入模块所在的目录:D:\test
- 执行多个模块的测试用例: python -m unittest Test_A_B Test_C
运行结果:
可见2个模块所有测试用例都被执行了
- 执行某个测试模块的某个测试类。 python -m unittest Test_A_B.Test_B
- 执行某个测试模块的某个测试类中的某个测试用例。 python -m unittest Test_C.Test_C.test_caseC3
1.2 UnitTest 测试用例自动发现
Unittest支持简单的测试搜索。若需要使用探索性测试,所有的测试文件必须是 模块 或包。
那么什么是模块,什么是包?
1.2.1 关于Python 中的模块和包
关于Python 中的模块
模块是包含 Python 定义和语句的文件。其文件名是模块名加后缀名 .py 。在模块内部,通过全局变量 name 可以获取模块名(即字符串)。简单理解就是我们一个一个的python文件。
通常我们可以通过IDE 运行模块 ,也可以在命令行通过Pyhton 命令运行模块。
比如 在当前目录, Python Test_A_B.py 注意一定要加后缀
标准模块:
Python 自带一个标准模块的库,它在 Python 库参考(此处以下称为”库参考” )里另外描述。 一些模块是内嵌到编译器里面的, 它们给一些虽并非语言核心但却内嵌的操作提供接口,要么是为了效率,要么是给操作系统基础操作例如系统调入提供接口。 这些模块集是一个配置选项, 并且还依赖于底层的操作系统
比如我们的Unittest 模块就被包含在标准库里。Pytest 则是第三方开发的开源包。
关于Python 中的包
包是一种用“点式模块名”构造 Python 模块命名空间的方法。例如,模块名 A.B 表示包 A 中名为 B 的子模块
从物理上看,包就是一个文件夹,在该文件夹下包含了一个 init.py 文件,该文件夹可用于包含多个模块源文件;从逻辑上看,包的本质依然是模块。
根据上面介绍可以得到一个推论,包的作用是包含多个模块,但包的本质依然是模块,因此包也可用于包含包
比如:
Unittest 就是一个包 ,而Test 文件夹也是一个包, 被包含在Unittest 里面,包可以包含包。 而 case.py 就是一个模块。
当然包的导入, 模块的导入,都是通过import 这里就不在赘述了。
1.2.2 Python中测试用例自动发现的实现
探索性测试在 TestLoader.discover() 中实现,但也可以通过命令行使用。它在命令行中的基本用法如下:cd project_directory python -m unittest discover
使用Python -m unittest 不带任何参数可以发现当前目录下的测试用例。Python -m unittest discover -v可以显示详细的测试结果。
Discover 可以使用的参数列表:
其中:
-f, —failfast
当出现第一个错误或者失败时,停止运行测试
2. UnitTest 测试用例的管理和组织
2.1 前言
我们先来回顾一下 第三节 关于断言的那个例子:
例子 3-2 example_3-2. py
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-16 20:00
import unittest
str0="xiao zhu "
str1="xiao he"
str2="xiao bing"
str3="xiao"
str4="xiao zhu "
class A():
pass
class B():
pass
a=A() #实例化A
b=B() #实例化B
def Demo_no_return():
print('no return')
def Demo_return():
return 'return'
class Assert_test_demo1(unittest.TestCase):
def test_case1(self):
print('case 1 验证arg1==arg2,不等则fail')
self.assertEqual(str0,str4,)
def test_case2(self):
print('case 2 验证arg1 != arg2, 相等则faill')
self.assertNotEqual(str0, str1)
def test_case3(self):
print('case 3 验证expr是true,如果为false,则fail')
self.assertTrue(1==1)
def test_case4(self):
print('case 4 验证expr是true,如果为false,则fail')
self.assertFalse(1>2)
def test_case5(self):
print('case 5 验证arg1、arg2是同一个对象,不是则fail')
self.assertIs(a,b,'a,b 不是一个对象 所以 Fail') #这里传入了测试Fail的msg
def test_case6(self):
print('case 6 验证arg1、arg2不是同一个对象,是则fail')
self.assertIsNot(a,b)
def test_case7(self):
print('case 7 验证expr是None,不是则fail') #可以用例判断是否采集或抓取到数据
self.assertIsNone(Demo_no_return())
def test_case8(self):
print('case 8 验证expr不是None,是则fail')
self.assertIsNotNone(Demo_return())
def test_case9(self):
print('case 9 验证arg1是arg2的子串,不是则fail')
self.assertIn(str3,str1) #
def test_case10(self):
print('case 10 验证arg1不是arg2的子串,是则fail')
self.assertNotIn(str1,str3)
def test_case11(self):
print('case 11 验证obj是cls的实例,不是则fail') #
self.assertIsInstance(a,A)
def test_case12(self):
print('case 12 验证obj不是cls的实例,是则fail')
self.assertNotIsInstance(b,A)
if __name__ == '__main__':
unittest.main(verbosity=2)
执行结果:
我们已经知道, 通过unittest.main可以收集执行本模块的所有测试用例, 但发现当前的测试用例执行的顺序并没有按照我们case代码编写的顺序执行。 那么该如何解决这个问题, 那么就需要用我们unitest的其它关键组件,对测试用例进行管理和组织
注:
unittest执行测试用例,默认是根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z
但在本例中, testcase10 11 12 由于是数字1开头,反而在test_case2 之前执行。
2.2 TestSuite与TextTestRunner结合-Testsuit.addTest()
先看下面的一个例子:
例子 4-2:
目录结构:
<br />** **<br />** run 包:**<br /> **case_make_suit.py** : 对test_case包里的测试用例进行组织 ,形成测试套件test_suit <br /> **run.py** : 程序的入口, 运行所有测试套件
**test_case 包:**<br /> 包含所有测试模块, 测试模块的测试类,及测试类中的测试用例<br /> <br />** 代码:**<br /> <br /> Test_A_B. py 和 Test_C.py 就是 例子4-1 <br /> Test_D.py 是例子 3-2 , 然后去掉 if __name__ == '__main__': 函数<br /> <br />** case_make_suit.py **
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
#导入test_case 包中的测试用例
from example_4_2.test_case.Test_A_B import *
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
def suit_1():
suit = unittest.TestSuite()#实例化 suit 对象
#增加 测试用例
suit.addTest(Test_A('test_caseA2'))
suit.addTest(Test_A('test_caseA1'))
suit.addTest(Test_A('test_caseA3'))
suit.addTest(Assert_test_demo1('test_case1'))
suit.addTest(Assert_test_demo1('test_case2'))
suit.addTest(Assert_test_demo1('test_case3'))
suit.addTest(Assert_test_demo1('test_case4'))
suit.addTest(Assert_test_demo1('test_case5'))
suit.addTest(Assert_test_demo1('test_case6'))
suit.addTest(Assert_test_demo1('test_case7'))
suit.addTest(Assert_test_demo1('test_case9'))
suit.addTest(Assert_test_demo1('test_case10'))
suit.addTest(Assert_test_demo1('test_case11'))
suit.addTest(Assert_test_demo1('test_case12'))
return suit # 范围suit对象
run.py
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
import unittest
from case_make_suit import *
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)#实例化运行对象
runner.run(suit_1())#调用run 方法并传入 suit_1()
运行结果:
test_caseA2 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA1 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA3 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_case1 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case2 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case3 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case4 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case5 (example_4_2.test_case.Test_D.Assert_test_demo1) … FAIL
test_case6 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case7 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case9 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case10 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case11 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case12 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
说明:
- runner 运行了所有添加到suit_1 的测试用例 ,并且是按照添加顺序执行测试用例
textTestRunner可以传入参数,来定制测试数据的形式。 verbosity=2 和unittest.mian()函数
里面参数是一个意思,显示详细测试结果,
很明显如果测试用例很多,一条一条加不现实, TestSuite 类还提供了addTests() 方法 通过列表形式传入
2.3 TestSuite与TextTestRunner结合-Testsuit.addTests()
例子 4-3:
在4-2的基础上增加一个suit_2
case_make_suit.py :
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
#导入test_case 包中的测试用例
from example_4_2.test_case.Test_A_B import*
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
def suit_2():
suit = unittest.TestSuite()
test_case_list= [Test_A('test_caseA1'),Test_A('test_caseA2'),Test_A('test_caseA3'),
Assert_test_demo1('test_case1'),Assert_test_demo1('test_case2'),Assert_test_demo1('test_case3'),
Assert_test_demo1('test_case4'), Assert_test_demo1('test_case5'), Assert_test_demo1('test_case6'),
Assert_test_demo1('test_case7'), Assert_test_demo1('test_case8'), Assert_test_demo1('test_case9'),
Assert_test_demo1('test_case10'), Assert_test_demo1('test_case11'), Assert_test_demo1('test_case12'),
]
suit.addTests(test_case_list)
return suit
run.py :
import unittest
from case_make_suit import *
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)#实例化运行对象
runner.run(suit_2())#调用run 方法并传入 suit_2()
运行结果:
test_caseA2 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA1 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA3 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_case1 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case2 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case3 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case4 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case5 (example_4_2.test_case.Test_D.Assert_test_demo1) … FAIL
test_case6 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case7 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case9 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case10 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case11 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case12 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
说明:
由于addTests 方法传入的是一个列表 ,而列表是可以循环操作的。 这样增加多个case 可以通过,case命名的规则进行批量添加
当然依然不够简便
2.4 使用加载器Testloader
unittest.TestLoader()提供了创建test suite的几种方法:
1.TestLoader().loadTestsFromTestCase(testCaseClass)<br /> 2.TestLoader().loadTestsFromModule(module, pattern=None)<br /> 3. TestLoader().loadTestsFromName(name, module=None)<br /> 4.TestLoader().loadTestsFromNames(name, module=None)<br /> 5.TestLoader().discover # 前面的 discover 出现了
下面一一列举其用法,并会感叹unittest的精妙之处。
- loadTestsFromTestCase(testCaseClass)<br />testCaseClass必须是TestCase的子类(或孙类也行)
- loadTestsFromModule(module, pattern=None)<br />test case所在的module
- loadTestsFromName(name, module=None)<br />name是一个string,string需要是是这种格式的“module.class.method”
- loadTestsFromNames(name, module=None)<br />names是一个list,用法与上同
- discover(start_dir, pattern=’test*.py’, top_level_dir=None)<br />从python文件中获取test cases
2.4.1 loadTestsFromTestCase(testCaseClass)
代码:
case_make_suit.py : 通过加载指定测试用例类,增加suit_load()_1实现
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
#导入test_case 包中的测试用例
from example_4_2.test_case.Test_A_B import *
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
def suit_loadCase_1():
suit = unittest.TestSuite()
testcase_1=unittest.TestLoader().loadTestsFromTestCase(Test_A)
testcase_2=unittest.TestLoader().loadTestsFromTestCase(Assert_test_demo1)
suit.addTests(testcase_1)
suit.addTests(testcase_2)
return suit
对应得run.py 模块也要做相应的修改呀,这里就不啰嗦了
运行结果:
test_caseA1 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA2 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA3 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_case1 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case10 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case11 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case12 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case2 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case3 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case4 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case5 (example_4_2.test_case.Test_D.Assert_test_demo1) … FAIL
test_case6 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case7 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case8 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case9 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
说明:
通过Testloader() 实例化对象, 然后调用 loadTestsFromTestCase ,传入类名做为
参数,传入类必须继承testcase 类
一个测试类可以包含很多测试用例, 而且只需传一次参就可以,当然很方便
case_make_suit.py : 通过加载指定测试用例类,增加suit_load()_2实现
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
import unittest
#这里直接从包里导出模块就行了
from example_4_2.test_case import Test_A_B
from example_4_2.test_case import Test_D
def suit_loadCase_2():
suit = unittest.TestSuite()
testcase_1=unittest.TestLoader().loadTestsFromModule(Test_A_B)
testcase_2 = unittest.TestLoader().loadTestsFromModule(Test_D)
suit.addTests(testcase_1)
suit.addTests(testcase_2)
return suit
运行结果:
test_caseA1 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA2 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseA3 (example_4_2.test_case.Test_A_B.Test_A) … ok
test_caseB1 (example_4_2.test_case.Test_A_B.Test_B) … ok
test_caseB2 (example_4_2.test_case.Test_A_B.Test_B) … ok
test_caseB3 (example_4_2.test_case.Test_A_B.Test_B) … ok
test_case1 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case10 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case11 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case12 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case2 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case3 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case4 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case5 (example_4_2.test_case.Test_D.Assert_test_demo1) … FAIL
test_case6 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case7 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case8 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
test_case9 (example_4_2.test_case.Test_D.Assert_test_demo1) … ok
说明:
- 通过模块导入 也之需要传入一个参数,那就是模块名称 ,前提是模块已经被导入
2.4.3 loadTestsFromName(name, module=None)
代码:
import unittest
from example_4_2.test_case.Test_A_B import *
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
from example_4_2.test_case import Test_A_B
from example_4_2.test_case import Test_D
import sys
sys.path.append(r'D:\test\example_4_2\test_case')
#需要将testcase 所在模块路径加入到python系统路径
def suit_loadCase_3():
suit = unittest.TestSuite()
testcase_1 = unittest.TestLoader().loadTestsFromName("Test_A_B.Test_A.test_caseA1")
#模块名.类名.方法名
testcase_2 = unittest.TestLoader().loadTestsFromName("Test_C.Test_C.test_caseC2")
suit.addTests(testcase_1)
suit.addTests(testcase_2)
return suit
运行结果:
说明 :
- 这个方法和addTest 方法功能类似, 由于是一个一个测试用例加入,在一般情况不建议使用 ,要注意一下这里传入的是一个引人格式的字符串
2.4.4 loadTestsFromNames(name, module=None)
代码:
import unittest
from example_4_2.test_case.Test_A_B import *
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
from example_4_2.test_case import Test_A_B
from example_4_2.test_case import Test_D
import sys
sys.path.append(r'D:\test\example_4_2\test_case')
#需要将testcase 所在模块路径加入到python系统路径
def suit_loadCase_4():
suit = unittest.TestSuite()
testcaselist=["Test_A_B.Test_A.test_caseA1","Test_C.Test_C.test_caseC2"]
testcase_1 = unittest.TestLoader().loadTestsFromNames(testcaselist)
suit.addTests(testcase_1)
return suit
运行结果:
说明 :
- 这个方法和addTests 方法功能类似, 这里传入的是一个引人格式的字符串list ,如果需要保证测试用例严格的执行次序要求可以通过这个方法
2.4.5 discover(start_dir,pattern=’test*.py’top_level_dir=None) (重要)
代码:
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00
import unittest
from example_4_2.test_case.Test_A_B import *
from example_4_2.test_case.Test_C import *
from example_4_2.test_case.Test_D import *
from example_4_2.test_case import Test_A_B
from example_4_2.test_case import Test_D
import sys
sys.path.append(r'D:\test\example_4_2\test_case')
def suit_loadCase_5():
suit = unittest.TestSuite()
testcase1 = unittest.TestLoader().discover(r'D:\test\example_4_2\test_case')
testcase2= unittest.TestLoader().discover(r'D:\test\example_4_2\test_case','Test_A*')
suit.addTests(testcase1)
suit.addTests(testcase2)
return suit
执行结果:
#testcase1 执行:
test_caseA1 (Test_A_B.Test_A) … ok
test_caseA2 (Test_A_B.Test_A) … ok
test_caseA3 (Test_A_B.Test_A) … ok
test_caseB1 (Test_A_B.Test_B) … ok
test_caseB2 (Test_A_B.Test_B) … ok
test_caseB3 (Test_A_B.Test_B) … ok
test_caseC1 (Test_C.Test_C) … ok
test_caseC2 (Test_C.Test_C) … ok
test_caseC3 (Test_C.Test_C) … ok
test_case1 (Test_D.Assert_test_demo1) … ok
test_case10 (Test_D.Assert_test_demo1) … ok
test_case11 (Test_D.Assert_test_demo1) … ok
test_case12 (Test_D.Assert_test_demo1) … ok
test_case2 (Test_D.Assert_test_demo1) … ok
test_case3 (Test_D.Assert_test_demo1) … ok
test_case4 (Test_D.Assert_test_demo1) … ok
test_case5 (Test_D.Assert_test_demo1) … FAIL
test_case6 (Test_D.Assert_test_demo1) … ok
test_case7 (Test_D.Assert_test_demo1) … ok
test_case8 (Test_D.Assert_test_demo1) … ok
test_case9 (Test_D.Assert_test_demo1) … ok
#testcase2 执行:
test_caseA1 (Test_A_B.Test_A) … ok
test_caseA2 (Test_A_B.Test_A) … ok
test_caseA3 (Test_A_B.Test_A) … ok
test_caseB1 (Test_A_B.Test_B) … ok
test_caseB2 (Test_A_B.Test_B) … ok
test_caseB3 (Test_A_B.Test_B) … ok
说明:
- discover()方法在 章节 1.2.2 Python中测试用例自动发现的实现 已经提到
- 这里使用了2个参数 , 第一个参数表示搜索的开始目录, 第二个参数指定模糊搜索的条件 , 通过2个参数的组合可以准确定义模块级别执行的测试用例
- dicover()使用相对简单, 适用于测试模块多, 测试用例数目的情况,是当前主要的收集组织测试用例的方法