我们想让将多个单测case 按照一定规则或者规范,统一一个入口执行。而不去人工手动每次单个执行,这就是测试集成的初衷吧。

unittest 可以基本实现我们想要的东西。官方文档:https://docs.python.org/zh-cn/3/library/unittest.html#

unittest 整体来讲分为如下几个大的模块(官方解读):

测试脚手架

test fixture 表示为了开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。

测试用例

一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。 unittest 提供一个基类: TestCase,用于新建测试用例。

测试套件

test suite 是一系列的测试用例,或测试套件,或两者皆有。它用于归档需要一起执行的测试。

测试运行器(test runner)

test runner 是一个用于执行和输出测试结果的组件。这个运行器可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。

下图借用原文地址:

unittest用于什么测试_unittest用于什么测试

具体操作我们来看如下demo:

1.测试用例

创建一个被测试对象以及方法 mathfunc.py:

def add(a, b):
    return a + b
def minus(a, b):
    return a - b
def multi(a, b):
    return a * b
def divide(a, b):
    return a / b

创建一个case 文件 testMathfunc.py

class TestMathfunc(unittest.TestCase):
    """Test mathfuc.py"""

    # setUp和tearDown在每次执行case前后都执行了一次

    def setUp(self):
        print("do something before testDemo.Prepare environment.")

    def tearDown(self):
        print("do something after testDemo.Clean up.")

    def test_add(self):
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    @unittest.skip("not run...")
    def test_minus(self):
        self.assertEqual(-1, minus(1, 2))
        self.assertNotEqual(0, minus(2, 2))

    def test_divide(self):
        """Test method divide(a, b)"""
        self.skipTest("测试跳过")
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        print("multi")
        self.assertEqual(6, multi(2, 3))

if __name__ == '__main__':
    unittest.main()

如上,一个完成一个case 主要分:

  • 1.开始之前环境部署,资源预制方法,def setUp(self):
  • 2.测试结束,环境恢复,资源恢复,def tearDown(self):
  • 3.测试内容,def test_***(self):,测试内容中应该有至少一次断言。
  • 4.测试执行,unittest.main()

另外:

setUp 和tearDown 是每次执行测试内容都会执行,有些时候我们想在这个cases 执行前后仅执行一次环境部署和环境恢复。unittest 提供了方法,

# 如果想要在所有case执行之前准备一次环境,
    # 并在所有case执行结束之后再清理环境,我们可以用 setUpClass() 与 tearDownClass()
    @classmethod
    def setUpClass(cls):
        print("This setUpClass() method only called once.")

    @classmethod
    def tearDownClass(cls):
        print("This tearDownClass() method only called once too.")

 还有:

当犹豫某些原因我们不想去执行或者需要达到某些条件时候采取执行一些case ,unittest 提供了跳过case 的一些装饰器,如下原文内容

@unittest.skip(reason)

跳过被此装饰器装饰的测试。 reason 为测试被跳过的原因。

@unittest.skipIf(conditionreason)

当 condition 为真时,跳过被装饰的测试。

@unittest.skipUnless(conditionreason)

跳过被装饰的测试,除非 condition 为真。

@unittest.expectedFailure

把测试标记为预计失败。如果测试不通过,会被认为测试成功;如果测试通过了,则被认为是测试失败。

exception unittest.SkipTest(reason)

引发此异常以跳过一个测试。

通常来说,你可以使用 TestCase.skipTest() 或其中一个跳过测试的装饰器实现跳过测试的功能,而不是直接引发此异常。

被跳过的测试的 setUp() 和 tearDown() 不会被运行。被跳过的类的 setUpClass() 和 tearDownClass() 不会被运行。被跳过的模组的 setUpModule() 和 tearDownModule() 不会被运行。

2.测试套件

如上case 中我们使用了main 方法执行单case,仅使用与调试使用,工厂模式下我们通常使用测试套件,就是将所有需要执行的测试用例放在一个套子里,然后我们通过执行这个套件,来执行所有case。

方法一

创建测试套件 test_suite.py,

if __name__ == '__main__':
    # 创建测试套件对象
    suite = unittest.TestSuite()
    # 添加测试用例
    tests=[TestMathfunc("test_add"),TestMathfunc("test_minus"),TestMathfunc("test_divide")]
    suite.addTests(tests)

    # 控制台打印输出
    runner = unittest.TextTestRunner()
    runner.run(suite)

这是最基础的测试套件运行测试用例方法,需要手工加入测试用例名,如果成千上万的测试用例,就无法这么操作了。

当然,unittest还提供了,测试用例加载器,我们只需要提供测试用例名,测试文件名,或者其他。

方法二

if __name__ == '__main__':
    # 创建测试套件对象
    suite = unittest.TestSuite()
    # 添加测试用例
    # tests=[TestMathfunc("test_add"),TestMathfunc("test_minus"),TestMathfunc("test_divide")]
    # suite.addTests(tests)

    # 测试文件名称
    # suite.addTests(unittest.TestLoader().loadTestsFromNames(["test_mathfunc.TestMathfunc"]))
    # 这种方法用于获取无文件和非官方的使用“加载”测试参数
    # suite.addTests(unittest.TestLoader().loadTestsFromModule(TestMathfunc02))
    # 测试用例名称
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathfunc))

    # 控制台打印输出
    runner = unittest.TextTestRunner()
    runner.run(suite)

这些都是针对于一个类中所有case 的一个集成测试。拿如果我们有多个类,或者多个文件时候,这些方法就不适用了。新版本3.7以后,我们可以使用discover方法只需要指定测试文件的文件夹地址即可,该方法同样适用以上情况如下:

方法三

if __name__ == '__main__':
   
    test_dir = './'
    suite = unittest.defaultTestLoader.discover("./testCases", pattern='test*.py', )
    # 控制台打印输出
    runner = unittest.TextTestRunner()
    runner.run(suite)

3.测试运行器(test runner)

如上,我们使用直接调用TestTestRunner() 方法,执行测试套件,结果只能打印在控制台。那么我们想存放输出的结果怎么办?

方法一

if __name__ == '__main__':

    test_dir = './'
    suite = unittest.defaultTestLoader.discover("./testCases", pattern='test*.py', )
    # 控制台打印输出
    # runner = unittest.TextTestRunner()
    # runner.run(suite)

    # 讲生成的结果存储在txt中
    with open('testresult.txt', 'a',encoding="utf-8") as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(suite)

如上我们已经能够永久性的查看执行过程和结果数据。但是对于公司使用,或者他人查看,我们 可能需要生成报告形式更直观些。这里我们使用HTMLTestRunner.py文件(感谢大神操作),该文件是开源的,但你找到的可能是这对2.7 版本的,我个人用的3.7,文件部分地方需要修改。直接将文件放百度网盘了自取:

地址:https://pan.baidu.com/s/1YKIrg53eaeyvklXJZVx85A

提取码:obvq

看到网上有人说放在你的python/lib下,但是我这边提示“no module”,个人放在工程目录下。顺便看下测试工程目录:

unittest用于什么测试_Test_02

然后我们使用HTMLTestRunner 生成html 报告文件:

if __name__ == '__main__':

    test_dir = './'
    suite = unittest.defaultTestLoader.discover("./testCases", pattern='test*.py', )
    # 控制台打印输出
    # runner = unittest.TextTestRunner()
    # runner.run(suite)

    # 讲生成的结果存储在txt中
    # with open('testresult.txt', 'a',encoding="utf-8") as f:
    #     runner = unittest.TextTestRunner(stream=f, verbosity=2)
    #     runner.run(suite)

    # 讲生成的结果存储在HTML中
    with open('result.html', encoding='utf-8', mode='w', ) as f:
        runner = HTMLTestRunner(stream=f, verbosity=2, title="MathFunc Test Report",
                                description='generated by HTMLTestRunner.', )
        runner.run(suite)

运行结果:

控制台输出:

unittest用于什么测试_unittest用于什么测试_03

报告文件:result.html

unittest用于什么测试_unittest用于什么测试_04

另外:

运行器可以指定参数,

1.TextTestRunner执行器中,verbosity参数0,1,2,不同等级信息,依次增多。

遗留问题:

如果让报告中展示sikp case ,如何在airttest 中使用unitest 集成测试。