一、unittest五大组件
-
test case:测试用例
unittest 提供一个基类TestCase
,用于新建测试用例。一个测试用例是一个独立的测试单元,unittest中测试用例方法都是以test开头的,且执行顺序会按照方法名的ASCII值排序。 -
test suite:测试套件
多个测试用例的集合,把需要一起执行的测试用例集中放到一块执行,使用TestLoader来加载测试用例到测试套件中。 -
test fixure:测试夹具
用于测试用例环境的搭建和销毁。测试前环境准备(setUp),执行测试代码(run),以及测试后环境还原(tearDown)。 -
test loader 测试加载器
加载TestCase到TestSuite中的,并创建它们的实例,然后添加到TestSuite中,返回TestSuite实例。 -
test runner 测试运行器
执行测试用例,并将测试结果保存到TextTestResult实例中。默认选择TextTestRunner(文本运行器),这个运行器可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。
二、TestCase用法
注:这里编译器使用的pyCharm 进行演示。
import unittest # 导入unittext包
class StadyUnittest(unittest.TestCase): # 定义类,继承unittest.TestCase方法
# 测试用例 1
def test01_case1(self):
print("测试1:case1")
# 测试用例 2
def test02_case2(self):
print("测试2:case2")
# 测试用例 3
def test03_case3(self):
print("测试3:case3")
注意这里无需写 main 函数也可以执行。鼠标在哪一个函数后面右击运行,即可对哪一个函数进行单元测试。如下图,选择测试 test01_case() 函数:
测试 test03_case3() 函数:
如果想执行所有测试用例,在类名处点击执行即可。
2.1 命令行运行TestCase
2.1.1 使用命令行执行测试单元
- 使用
python -m unittest stady_test.StadyUnittest
命令可以达到上述相同的执行效果。
- 使用
-v
参数可以输出更详细的报告。另外,我们也可以只指定类中的某个测试用例执行。
或者我们也可以使用-k
匹配测试用例选择那些测试用例去执行。其中 -k 有两种匹配方法。-k
,*
,key
:如果有通配符 * ,则表示以 “key” 结尾的测试单元匹配-k
,key
: 不好通配符 * 啧表示,只有含有 “key” 就匹配
2.1.2 常见的unittest命令行参数
- 除此之外,我们还可以使用
python -m unittest -h
获得unittest 的命令参数 。
选项 | 说明 |
---|---|
-b, --buffer | 在测试运行时,标准输出流与标准错误流会被放入缓冲区。成功的测试的运行时输出会被丢弃;测试不通过时,测试运行中的输出会正常显示,错误会被加入到测试失败信息。 |
-c, --catch | 当测试正在运行时, Control-C 会等待当前测试完成,并在完成后报告已执行的测试的结果。当再次按下 Control-C 时,引发平常的 KeyboardInterrupt 异常。 |
-f, --failfast | 当出现第一个错误或者失败时,停止运行测试。 |
-k | 只运行匹配模式或子串的测试方法和类。可以多次使用这个选项,以便包含匹配子串的所有测试用例。 包含通配符(*)的模式使用 fnmatch.fnmatchcase() 对测试名称进行匹配。另外,该匹配是大小写敏感的。 模式对测试加载器导入的测试方法全名进行匹配。 例如,-k foo 可以匹配到 foo_tests.SomeTest.test_something 和 bar_tests.SomeTest.test_foo ,但是不能匹配到 bar_tests.FooTest.test_something 。 |
–locals | 在回溯中显示局部变量。 |
-v, --verbose | 更详细地输出结果。 |
-s, --start-directory directory | 开始进行搜索的目录(默认值为当前目录 . )。 |
-p, --pattern pattern | 用于匹配测试文件的模式(默认为 test*.py )。 |
-t, --top-level-directory directory | 指定项目的最上层目录(通常为开始时所在目录)。 |
- 需要知道的是,我们使用编译右键运行的方式,最终也是通过命令行方式执行的,我们运行的每一种不同的实例,在我们右键鼠标运行时,编译器自动帮我们进行了相关参数的配置。
如图,我们的stady_test.StadyUnittest.test01_case1
已经在配置中为我们生成了。
2.2 使用unittest.main() 方法
在源文件中添加如下代码:
if __name__ == '__main__':
unittest.main()
2.2.1 配置PyCharm执行
如果想以这种方式运行程序,我们需要配置一下编译器。
- 第一步,点击编辑配置
- 第二步,选择 + 添加
- 第三步,选择python文件
- 第四步,将文件路径填写完整。直接点击即选择目录即可。
- 第五步,配置完成后点击应用。我们就有了一个当前文件的配置。点击右上角绿色三角形即可执行。
接下来,选择好配置,直接执行即可。
上述方法等同于直接在命令行运行python stady_test.py
。
2.2.2 测试结果分析
测试结束在屏幕输出的文本信息中有四种类型:
形式 | 表示类型 |
---|---|
. | 成功 |
F | 失败 |
E | 错误 |
S | 跳过 |
下面使用如下代码,分别输出上述四种状态。
import unittest # 导入unittext包
class StadyUnittest(unittest.TestCase): # 定义类,继承unittest.TestCase方法
# 测试用例 1:正常执行
def test01_case1(self):
print("测试1:case1")
# 测试用例 2:异常
def test02_case2(self):
raise Exception("测试用例2 异常")
print("测试2:case2")
# 测试用例 3:错误
def test03_case3(self):
print("测试3:case3")
self.assertEqual(1, 2) # 断言出错
# 测试用例 4:跳过
@unittest.skip("跳过case4") # 装饰器,用于跳过此用例
def test04_case4(self):
print("测试4:case4")
if __name__ == '__main__':
unittest.main()
- pyCharm执行结果
- 命令行执行结果
2.2.3 测试用例的执行顺序
unittest.main()
方法默认执行所有的测试用例,并且测试用例的执行是按照一定的顺序执行的 。
上述代码中,我们设置了四个测试单元函数,他们的函数名均以test开头,编号为 00、01、02、03 。目的就是让他们按照一定的执行顺序执行下去。
因为,单元测试用例的执行顺序就是以ascll码的顺序执行的。例如我们可以新建一个python文件,写下如下代码进行测试。
import unittest # 导入unittext包
class StudyUnittest(unittest.TestCase): # 定义类,继承unittest.TestCase方法
def testa_case(self):
print("case4 : testa_...")
def testZ_case(self):
print("case3 : testZ_...")
def test01_case(self):
print("case1 : test01_...")
def test20_case(self):
print("case2 : test20_...")
if __name__ == '__main__':
name = [a for a in dir(StudyUnittest) if a.startswith('test')]
print("name: ", name)
print("sort: ", sorted(name)) # 输出排序后的 name
print("执行顺序:")
unittest.main()
执行结果:按照 0 ~ 9,A ~ Z,a ~ z 的顺序。
2.3 分析unittest.main() 方法
unittest.main() 函数有很多参数,下面是unittest文件中 main() 的函数声明
unittest.main(
module='__main__', # 测试用例所在的路径,__main__表示当前模块
defaultTest=None, # 待测用例额的名称,默认是所有
argv=None, # 接收外部的参数
testRunner=None, # 测试运行器,默认是文本测试运行器 TextTestRunner
testLoader=unittest.defaultTestLoader, # 测试用例加载器
exit=True, # 是否在测试完成之后退出程序
verbosity=1, # 表示测试结果的信息复杂度,有0、1、2 三个值. -v
failfast=None, # 用例遇到失败后停止,不在执行后面的用例 -f
catchbreak=None, # ctrl-c 中断后执行完输出测试结果 -c
buffer=None, # 在测试运行期间缓冲stdout和stderr -b
warnings=None # 显示警告信息
)
以上为 unittest.main() 中的参数信息,下面简单的测试一下其中的几个参数选项:
- 使用 defaultTest 参数
if __name__ == '__main__':
unittest.main(defaultTest=["StudyUnittest.test02_case"])
- 使用 verbosity 参数
类似命令行参数中的-v
选项,0,1,2分别表示输出结果的详细程度,默认为1 。
if __name__ == '__main__':
print("\n_______________v = 0 :")
unittest.main(verbosity=0,exit=False)
print("\n_______________v = 1 :")
unittest.main(verbosity=1,exit=False)
print("\n_______________v = 2 :")
unittest.main(verbosity=2,exit=True)
- 使用 argv 参数
参数第一位是文件名: study_test3.py ,第二位是运行的模块名 StudyUnittest.test01_case
if __name__ == '__main__':
a = unittest.main(argv=['study_test3.py', 'StudyUnittest.test01_case'])
三、TestSuite用法
3.1 使用TestSuite进行测试
使用TestSuite进行测试的步骤有三步:
- 使用
unittest.TestSuite()
生成一个套件实例 - 使用TestSuite的
addTest()
或addTests()
把测试用例加载到套件中。
其中前者每次只加载一个测试用例,后者可一次加载多个测试用例。 - 调用unittest.main() 方法选择对应套件执行测试。
除了使用unittest.main()方法执行之外,还可以使用 run() 方法。具体请看 3.2、3.3 小节。
3.1.1 使用unittest.main()方法执行套件
import unittest
class StudyUnittest(unittest.TestCase):
def test01_case(self):
print("test01_case")
def test02_case(self):
print("test02_case")
def test03_case(self):
print("test03_case")
if __name__ == '__main__':
suite = unittest.TestSuite() # 生成一个套件
suite.addTest(StudyUnittest("test01_case")) # 加载测试用例到套件
suite.addTest(StudyUnittest("test02_case")) # 加载测试用例到套件
unittest.main(defaultTest='suite') # 选择默认执行的测试用例为 suilt 套件
执行结果:
3.1.2 使用TextTestRunner().run()方法执行套件
除此之外,我们也可以使用测试运行器运行该套件。
if __name__ == '__main__':
suite= unittest.TestSuite() # 生成一个套件
suite.addTest(StudyUnittest("test01_case")) # 加载测试用例到套件
suite.addTest(StudyUnittest("test02_case")) # 加载测试用例到套件
# unittest.main(defaultTest='suilt') # 选择默认执行的测试用例为 suilt 套件
# 使用运行器运行该套件
unittest.TextTestRunner().run(suite)
因为调用 unittest.main() 方法,最终都将转为调用 unittest.TextTestRunner() 方法。
- 其中 unittest.main() 具体的步骤为:
- 编写TestCase,由TestLoader加载TestCase到TestSuite
- 然后由TextTestRunner来运行TestSuite
- 最后将运行的结果保存在TextTestResult中。
而在 TextTextRunner 中实际上调用的 run() 方法执行的。
由此,我们可以使用函数定义我们的测试套件,通过以下方式进行测试:
# 定义测试套件生成函数
def suite():
suite = unittest.TestSuite()
suite.addTest(StudyUnittest("test01_case")) # 加载测试用例到套件
suite.addTest(StudyUnittest("test02_case")) # 加载测试用例到套件
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
3.1.3 使用TestSuite的run()方法执行套件
在pytthon 开发文档中显示 unittest.TestSuite()
中也存在一个 run()
方法。
unittest.TestSuite().run()
运行与此套件关联的测试,将结果收集到作为结果传递的测试结果对象中。 请注意,与TestCase.run()不同,TestSuite.run()要求传递结果对象。
同时我们可以查看TestCase类中,也有一个 run() 方法。
- 参考: unittest之TestSuite类详解 这篇文章
测试套件(TestSuite)的执行是通过它的run方法,而它的run方法又会去调用每一个用例(TestCase)的run方法。这样就实现了用例的执行
而我们之前提到的,unittest.main()的执行最终是调用了TextTestRunner.run()方法,而TextTestRunner.run()方法又根据每个suite去调用TestSuite.run()或TestCase.run() 去执行测试的。
if __name__ == '__main__':
#实例化一个TextTestResult类
result = unittest.TextTestResult(sys.stdout,'test result',1)
suite = unittest.TestSuite(map(StudyUnittest,['test01_case']))
'''
上面语句等同于 创建一个TestSuite实例,并向其中添加一个estCase
suite = unittest.TestSuite()
suite.addTest(StudyUnittest('test01_case'))
'''
suite.run(result)
# 使用TestCase实例的run()方法执行
testcase = StudyUnittest('test02_case')
testcase.run(result)
3.3.4 使用addTests()加载测试用例集
addTests() 接收的参数是一个列表类型,函数原型:def addTests(self, tests: Iterable[_TestType]) -> None:
。
下面演示用法:
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = [ # 测试用例集
StudyUnittest("test01_case"),
StudyUnittest("test02_case")
]
suite.addTests( testcases) # 加载测试用例集
unittest.main(defaultTest='suite')
四、TestLoader用法
我们发现使用 addTests() 与使用 addTest() 加载测试用例都太过麻烦,那么我们可以使用测试加载器进行测试用例的加载。
4.1 使用discover()方法加载测试用例
我们使用TestLoader 类中提供的discover()方法。 discover(start_dir,pattern='test*.py',top_level_dir= None)
- 该方法可以找到指定目录下所有测试模块,并可递归查到子目录下的测试模块,只有匹配到文件名时才加载
- start_dir:要测试的模块名或测试用例目录
- pattern=‘test*.py’:表示用例文件名的匹配原则。此处匹配以“test”开头的.py 类型的文件,* 表示任意多个字符
- top_level_dir= None:测试模块的顶层目录,如果没有顶层目录,默认为None
import os
import unittest
class StudyUnittest(unittest.TestCase):
def test01_case(self):
print("test01_case")
def test02_case(self):
print("test02_case")
def test03_case(self):
print("test03_case")
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(
start_dir=os.getcwd(), # 目录:os.getcwd()当前目录路径
pattern='*.py' # 文件: 所有以 .py 结尾的文件
)
suite.addTests( testcases) # 加载测试用例集
unittest.main(defaultTest='suite')
测试结果:
4.1 unittest.TestLoader提供的方法
除了上述示例中的discover()方法,TestLoader还提供了其他方法,下面将列出一部分内容仅供参考。
- 参考:https://docs.python.org/zh-cn/3/library/unittest.html?highlight=unittest#loading-and-running-tests
TestLoader类用于从类和模块创建测试套件。通常,不需要创建该类的实例;unittest模块提供了一个可以作为unittest. defaulttestloader共享的实例。但是,使用子类或实例允许自定义一些可配置属性。
loadTestsFromTestCase(testCaseClass)
-
返回由TestCase派生的testCaseClass中包含的所有测试用例的套件。
-
将为每个由getTestCaseNames()命名的方法创建一个测试用例实例。 默认情况下,这些是以test开头的方法名称。 如果getTestCaseNames()不返回任何方法,但实现了runTest()方法,则将为该方法创建一个测试用例。
loadTestsFromModule(module, pattern=None)
- 返回给定模块中包含的所有测试用例的套件。 此方法在模块中搜索从TestCase派生的类,并为为该类定义的每个测试方法创建该类的实例。
loadTestsFromName(name, module=None)
-
给定一个字符串说明符,返回所有测试用例的套件。
-
指定者名称是“点名”,可以解析为模块,测试用例类,测试用例类中的测试方法,TestSuite实例或返回TestCase或TestSuite实例的可调用对象。 这些检查按照此处列出的顺序进行; 也就是说,将在可能的测试用例类上的方法选择为“测试用例类内的测试方法”,而不是“可调用对象”。
getTestCaseNames(testCaseClass)
- 返回在testCaseClass中找到的方法名称的排序序列; 这应该是TestCase的子类。
discover(start_dir, pattern=‘test*.py’, top_level_dir=None)
- 通过从指定的起始目录递归到子目录中,找到所有测试模块,然后返回包含它们的TestSuite对象。 仅加载与模式匹配的测试文件。 (使用外壳样式模式匹配。)仅加载可导入的模块名称(即有效的Python标识符)。