1. 什么是单元测试
1.1 定义
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
1.2 单元测试中最核心的四个概念
test case(测试用例)
:
- 一个testcase的实例就是一个测试用例。测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)
TestSuite(测试套件)
:
- 多个测试用例集合在一起。
TestLoader(测试用例加载器)
:
- 用来加载Testcase到TestSuite中。
TextTestRunner(测试运行器)
:
- 用来执行测试用例,其中run(test)会执行TestSuite/TestCase中的run(result)方法。
test fixture(测试脚手架)
:
- 测试环境数据准备和数据清理或者测试用例环境的搭建和销毁。
1.3 unittest的静态类图
综上,整个流程就是首先要写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中。
2. 使用示例
以下所用到的自定义模块都在my_test包下
2.1 初级使用
# hello.py
class AddSub:
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a+self.b
def sub(self):
return self.a-self.b
# test_hello.py
import unittest
from my_test.hello import AddSub
class MyTest(unittest.TestCase):
# 每执行一个用例,都会执行setup()和teardown()方法
# 如果跑所有用例,只运行一次前提条件和结束条件。则用setupclass()和teardownclass()
@classmethod
def setUpClass(cls):
cls.add_sub = AddSub(1, 10)
# @classmethod
# def tearDownClass(cls):
# pass
#
# def setUp(self):
# pass
#
# def tearDown(self):
# pass
# unittest框架默认根据ASCII码的顺序加载测试用例的,数字与字母的顺序为:“0-9”,“A-Z”,“a-z”。而想要用例按顺序执行,需要通过TestSuite类的addTest()和addTests()方法按照一定的顺序来加载
def test_1_add(self):
result = self.add_sub.add()
self.assertEqual(11, result)
def test_0_sub(self):
result = self.add_sub.sub()
self.assertEqual(-9, result)
if __name__ == '__main__':
# unittest提供了一个测试脚本的命令行接口main。当在命令行运行该测试脚本,脚本按照特定格式输出
unittest.main(verbosity=2) # 0 是简单报告、1 是一般报告、2 是详细报告
"""
运行结果:
Testing started at 12:47 ...
"D:\Program Files\Python3.6.6\python.exe" "D:\Program Files\JetBrains\PyCharm 2019.1.1\helpers\pycharm\_jb_unittest_runner.py" --target test_hello.MyTest
Launching unittests with arguments python -m unittest test_hello.MyTest in E:\Project\PyCharm\test\my_test
Ran 2 tests in 0.003s
OK
Process finished with exit code 0
"""
2.2 高级使用
# hello.py
class AddSub:
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a+self.b
def sub(self):
return self.a-self.b
import unittest
from my_test.hello import AddSub
class MyTest(unittest.TestCase):
# 每执行一个用例,都会执行setup()和teardown()方法
# 如果跑所有用例,只运行一次前提条件和结束条件。则用setupclass()和teardownclass()
@classmethod
def setUpClass(cls):
cls.add_sub = AddSub(1, 10)
# @classmethod
# def tearDownClass(cls):
# pass
#
# def setUp(self):
# pass
#
# def tearDown(self):
# pass
# unittest框架默认根据ASCII码的顺序加载测试用例的,数字与字母的顺序为:“0-9”,“A-Z”,“a-z”。而想要用例按顺序执行,需要通过TestSuite类的addTest()和addTests()方法按照一定的顺序来加载
def test_1_add(self):
result = self.add_sub.add()
self.assertEqual(11, result)
def test_0_sub(self):
result = self.add_sub.sub()
self.assertEqual(-9, result)
# @unittest.skip(reason):无条件跳过测试,reason描述为什么跳过测试。
# @unittest.skip('强制跳过')
# def test_2(self):
# pass
#
# # @unittest.skipif(conditition,reason):condititon为条件,当条件为true时则跳过测试。
# @unittest.skipIf(10 > 5, "满足条件则跳过")
# def test_3(self):
# pass
#
# # @unittest.skipunless(condition,reason):condition为条件,与上面相反,当条件不是true时则跳过测试。
# @unittest.skipUnless(10 > 5, "不满足条件则跳过")
# def test_4(self):
# pass
#
# # @unittest.expectedFailure():把测试标记为预计失败。如果测试不通过,会被认为测试成功;如果测试通过了,则被认为是测试失败。
# @unittest.expectedFailure()
# def test_5(self):
# pass
注:
- 被跳过的测试的 setUp() 和 tearDown() 不会被运行。
- 被跳过的类的 setUpClass() 和 tearDownClass() 不会被运行。
- 被跳过的模组的 setUpModule() 和 tearDownModule() 不会被运行。
# 123.py
import unittest
import os
from HTMLTestRunnerNew import HTMLTestRunner
from my_test.test_hello import MyTest
from my_test import test_hello
# 创建测试套件对象
suit = unittest.TestSuite()
# discover可将某个目录下以"test_" 开头的模块加载
loader1 = unittest.TestLoader().discover(os.getcwd())
# loadTestsFromTestCase(testCaseClass):从某个类中加载所有的测试方法,参数为加载的类名,testCaseClass必须继承于TestCase。
loader2 = unittest.TestLoader().loadTestsFromTestCase(MyTest)
# loadTestsFromModule(module, pattern=None) :从某个模块中加载所有的测试方法,参数为模块名,即文件名。当该模块中有多个类都继承于TestCase时,那么这些类里的测试方法均会被执行。
loader3 = unittest.TestLoader().loadTestsFromModule(test_hello)
# loadTestsFromName(name, module=None) :加载某个单独的测试方法,参数name是一个string,格式为“module.class.method”。
loader4 = unittest.TestLoader().loadTestsFromName("my_test.test_hello.MyTest.test_1_add")
# loadTestsFromNames(name, module=None) :names是一个list,用法与loadTestsFromName相同。
loader5 = unittest.TestLoader().loadTestsFromNames(["my_test.test_hello.MyTest.test_1_add", "my_test.test_hello.MyTest.test_0_sub"])
# addTest将某个测试用例中的某个测试加入suit
suit.addTest(MyTest("test_0_sub"))
# addTest的参数还可以是“loader1, loader2, loader3, loader4, loader5”中的任意一个
suit.addTest(loader1)
# 内部使用的依然是addTest
suit.addTests([loader2, loader3, loader4, loader5])
# 将测试结果在控制台输出
# runner = unittest.TextTestRunner()
# runner.run(suit)
# 把测试结果以txt文件,在控制台不会输出。
# f = open("单元测试报告.txt", "w")
# runner = unittest.TextTestRunner(stream=f, verbosity=2) # 注意还有一些其它参数的使用 # 0 是简单报告、1 是一般报告、2 是详细报告
# runner.run(suit)
# f.close()
# 把测试结果以html的格式输出到页面上,并在控制台输出。
f = open("单元测试报告.html", "wb")
runner = HTMLTestRunner(stream=f, title="单元测试报告", description="单元测试报告", tester="zhangsan") # 注意还有一些其它参数的使用
runner.run(suit)
"""
控制台运行结果:
ok test_0_sub (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_0_sub (my_test.test_hello.MyTest)
ok test_0_sub (test_hello.MyTest)
ok test_1_add (test_hello.MyTest)
ok test_0_sub (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_0_sub (test_hello.MyTest)
ok test_1_add (my_test.test_hello.MyTest)
ok test_0_sub (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_1_add (test_hello.MyTest)
ok test_1_add (my_test.test_hello.MyTest)
ok test_1_add (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_0_sub (my_test.test_hello.MyTest)
ok test_1_add (my_test.test_hello.MyTest)
ok test_0_sub (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_1_add (my_test.test_hello.MyTest)
Time Elapsed: 0:00:00
Fri Jul 24 12:50:38 2020 - Start Test:test_0_sub (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_1_add (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_1_add (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_1_add (my_test.test_hello.MyTest)
Fri Jul 24 12:50:38 2020 - Start Test:test_0_sub (my_test.test_hello.MyTest)
Process finished with exit code 0
"""
生成的html页面:
HTMLTestRunnerNew.py文件链接,直接将其解压,复制到python的Lib文件夹下。
3. 断言常用方法
4. TestResult的属性
TestResult的属性几乎涵盖了我们需要对当前测试了解的所有信息,下面抽取其中最重要的属性来进行解释。
failfast
:值为True或False,当设置为True时,测试过程中遇到失败或者错误,则立即终止后续的测试,通常我们保持False即可。
failures
:
- 失败,这里会列出使用断言方法失败的情况。
errors
:
- 错误,这里会列出程序出现的异常错误。
testsRun
:
- 已经运行的所有测试的数量。
skipped
:
- 列出跳过的测试方法及原因。
expectedFailures
:
- 列出预期失败的方法。
unexpectedSuccesses
:
- 列出标记为预期失败,但实际运行却又成功的方法。
5. 命令行界面
[参考博客及文档]
python的单元测试unittest(一)Python的unittest框架python unittest自动化测试框架总结Unittest高级应用python手册_unittest — 单元测试框架