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的静态类图

python 数据VIF检验_单元测试

综上,整个流程就是首先要写好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页面:

python 数据VIF检验_Test_02


HTMLTestRunnerNew.py文件链接,直接将其解压,复制到python的Lib文件夹下。

3. 断言常用方法

python 数据VIF检验_python 数据VIF检验_03


python 数据VIF检验_单元测试_04

4. TestResult的属性

TestResult的属性几乎涵盖了我们需要对当前测试了解的所有信息,下面抽取其中最重要的属性来进行解释。

failfast:值为True或False,当设置为True时,测试过程中遇到失败或者错误,则立即终止后续的测试,通常我们保持False即可。

failures

  • 失败,这里会列出使用断言方法失败的情况。

errors

  • 错误,这里会列出程序出现的异常错误。

testsRun

  • 已经运行的所有测试的数量。

skipped

  • 列出跳过的测试方法及原因。

expectedFailures

  • 列出预期失败的方法。

unexpectedSuccesses

  • 列出标记为预期失败,但实际运行却又成功的方法。

5. 命令行界面

python 数据VIF检验_python_05


python 数据VIF检验_单元测试_06



[参考博客及文档]
python的单元测试unittest(一)Python的unittest框架python unittest自动化测试框架总结Unittest高级应用python手册_unittest — 单元测试框架