单元测试四: 一个unittest单元测试框架示例

testCase:测试用例,为测试某个功能(通常是函数或类),而编写的一组测试输入、执行条件以及预期结果的代码;

以便测试某个程序是否满足预期的需求。

 

unittest的4个重要概念:

1.Test FixTure:就是一个测试环境的搭建setUp(self)和销毁tearDown(self),这就是一个fixture。

setUp()--比如,某个文件或目录必须存在、数据库需要初始化好、网络服务要准备好、访问的URL需要登录授权完毕等等。tearDown()---清理和还原的功效,这样不至于各个测试执行的时候有环境污染造成各种诡异情况。比如:删除测试生成的文件或目录、销毁测试用的数据库等等。每次调用测试后,都会执行这个方法,即使调用的测试错误(Error)也会调用

2.Test Case:测试用例,继承unittest.TestCase的类。

3.Test Suite:测试套件,包含了多个测试用例或测试套件的集合

4.test runner:跑测试的家伙。

 

测试错误(异常),可以简单理解成测试代码执行时候报错了,比如:测试代码中print a,而a没有进行变量声明。

测试失败(非异常,但没有得到预期的结果),可以简单理解成测试代码执行正常,但没有得到预期的测试结果,比如:测试代码中调用功能代码add(1, 2),但返回结果不是3。

测试忽略(Ignore):skip装饰器

 

单元测试的加载,两种方式:unitest.main()和testsuite

一、unittest.main()示例:通过unittest.main()来启动单元测试的测试模块

import unittest
class BaiduTest(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(BaiduTest, self).__init__(*args, **kwargs)
        # unittest.TestCase.__init__(*args, **kwargs)
    def setUp(self) -> None:
        print("setup=================")
    def test_case1(self):
        print("这是case1")
        self.assertEqual(1, 1, "1!=1")
        # print(aaa)
    def test_case2(self):
        # print(a)
        self.assertEqual(1, 2, "1!=2")
        print("1323")
        print(abc)
    def tearDown(self) -> None:
        print("删除生成的文件,销毁次单元测试生成的一切环境")
if __name__ == "__main__":
    unittest.main()

 

注意:

  1. 每个case,都会分别启用一次setUp和tearDown。
  2. 当抛出异常的时侯,无论是出现测试错误,还是测试失败,此case后面的代码不会继续执行。
  3. 以上结论不论是使用unitest.main()还是testsuite都适用

测试结果:

setup=================
这是case1
删除生成的文件,销毁次单元测试生成的一切环境
setup=================
删除生成的文件,销毁次单元测试生成的一切环境
.F
======================================================================
FAIL: test_case2 (__main__.BaiduTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/untitled/1.py", line 14, in test_case2
    self.assertEqual(1, 2, "1!=2")
AssertionError: 1 != 2 : 1!=2

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

 

  

二.testsuite加载集合:通过添加到testsuite集合中,再加载所有的被测试对象;而testsuite里存放的就是单元测试的用例。

import unittest

class MyTestCase1(unittest.TestCase):
    def setUp(self):
        print("启动Case1")
    def test_case1(self):
        print("这是case1测试用例1")
        # 断言
        self.assertEqual(1, 1, '不相等')
    def test_case2(self):
        print("这是case1测试用例2")
        # 断言
        self.assertEqual(1, 1, '不相等')
    def tearDown(self):
        print("结束Case1")

class MyTestCase2(unittest.TestCase):
    def setUp(self):
        print("启动Case2")
    def test_case1(self):
        print("这是case2测试用例1")
        # 断言
        self.assertEqual(1, 1, '不相等')
    def test_case2(self):
        print("这是case2测试用例2")
        # 断言
        self.assertEqual(1, 1, '不相等')
    def tearDown(self):
        print("结束Case2")

if __name__ == '__main__':
    # 1.测试用例集合1
    suite1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase1)
    # 1.测试用例集合2
    suite2 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase2)
    # 2.加载所有的被测试对象:将所有的测试用例集合,加在一起
    suite = unittest.TestSuite([suite1, suite2])
    # 3.运行testsuite测试
    unittest.TextTestRunner(verbosity=2).run(suite)

 

 测试结果:

C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/PycharmProjects/untitled/1.py
test_case1 (__main__.MyTestCase1) ... ok
test_case2 (__main__.MyTestCase1) ... ok
test_case1 (__main__.MyTestCase2) ... ok
test_case2 (__main__.MyTestCase2) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
启动Case1
这是case1测试用例1
结束Case1
启动Case1
这是case1测试用例2
结束Case1
启动Case2
这是case2测试用例1
结束Case2
启动Case2
这是case2测试用例2
结束Case2

 

 补充:

1.生成单个suite套件的方式:

既可以用loader---unittest.TestLoader(),然后loader.loadTestsFromTestCase(case类)得到;以上示例所使用的方法。

也可以用unittest.makeSuite(Case类);以下示例所使用的方法。

也可以用在只添某一个测试项时,跳过loader或makeSuite,直接实例化case类,Case("某测试项");其方法总结,见后章节。

2. 组装多个suite套件的方式:

可以在初始化unittest.TestSuite([suit1, suit2, case1, case2])类的时侯,在初始化方法中,传入suite或case实例对象;

也可以,先初始化一个空的suite集合,在后面它的使用addTest或addTests方法添加。

上面示例中,生成suite的方法,是在初始化TestSuite类的时侯,添加suite或case:suite = unittest.TestSuite([suite1, suite2])

也可以,在初始化的时侯不添加测试用例case或测试套件suite,而在后面调addTest或addTests来添加。

# 方式一:实始化的时侯,添加测试用例或测试套件
suite = unittest.TestSuite([suite1, suite2])
# 方式二:调用addTest或addTests方法添加
suite = unittest.TestSuite()
suite.addTests(suite1)
suite.addTests(suite2)

3.以上示例中的TestLoader()方法:

 上面示例中,生成suite,使用的是TestLoader方式将测试用例添加到测试套件中: 

suite1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase1) 

 TestLoader是用来加载TestCase到TestSuite中的 

TextTestRunner()是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法 

测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。 

4.以上的整个流程就是首先要写好TestCase,然后由TestLoader加载TestCase整个类的case到生成单个suite套装,再使用TestSuite将所有单个的suite再组装成suite集合,然后由TextTestRunner来运行suite集合,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中。

三、makeSuite(case类)方法:

case类("某测试项")方法: 

把某测试用例的所有测试方法都添加到suite,makeSuite方法:suite.addTest(unittest.makeSuite(MyTestCase1))

只添加某测试用例的某个测试方法添加到suite:suite.addTest(MyTestCase1('test_case1'))

#-*- encoding: UTF-8 -*-
import unittest
class ExampleTestCase(unittest.TestCase):
    def test_do_somthing(self):
        self.assertEqual(1, 1)
    def test_do_somthing_else(self):
        self.assertEqual(1, 1)
class AnoterExampleTestCase(unittest.TestCase):
    def test_do_somthing(self):
        self.assertEqual(1, 1)
    def test_do_somthing_else(self):
        self.assertEqual(1, 1)
def suite_use_make_suite():
    """想把TestCase下的所有测试加到TestSuite的时候可以这样用
    """
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(ExampleTestCase))
    return suite
def suite_add_one_test():
    """想把TestCase下的某个测试加到TestSuite的时候可以这样用
    """
    suite = unittest.TestSuite()
    suite.addTest(ExampleTestCase('test_do_somthing'))
    return suite
def suite_use_test_loader():
    """想用TestLoader方式把测试加到TestSuite的死后可以这样用
    """
    test_cases = (ExampleTestCase, AnoterExampleTestCase)
    suite = unittest.TestSuite()
    for test_case in test_cases:
        tests = unittest.defaultTestLoader.loadTestsFromTestCase(test_case)
        suite.addTests(tests)
    return suite
if __name__ == '__main__':
    unittest.main(defaultTest='suite_use_test_loader')

 测试结果:

....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

 

 四、跳过某个case:skip装饰器

skip装饰器一共有三个 :

  • unittest.skip(reason)
  • unittest.skipIf(condition, reason)
  • unittest.skipUnless(condition, reason)

skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。 

五、用HTMLTestRunner输出漂亮的HTML报告

# -*- coding: utf-8 -*-

import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

    with open('HTMLReport.html', 'w') as f:
        runner = HTMLTestRunner(stream=f,
                                title='MathFunc Test Report',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                )
        runner.run(suite)

 verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告

参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。

六、doctest:直接把测试写在文档注释。其中一个优点是,看到注释就知道这个模块、函数、类是怎么个用法了;而其中一个缺点是,测试代码的组织上很难模块化。

def show_me_the_money():
    """
    >>> print show_me_the_money()
    $
    """
    return '$'
if __name__ == '__main__':
    import doctest
    doctest.testmod()

 执行python test_doctest.py -v,即可。  

  • 还有各种成熟的各种开源开发库,比如:Python的Web开发框架Django,它里面就提供了适合Web开发场景的单元测试各种类库。
  • 还有需要模拟各种情况的类库,比如:网络请求、数据库存储、读写文件等等,Python中就提供了不少好的模拟的库,如 Mock
 

 

posted on 2018-08-24 22:01  myworldworld  阅读(498)  评论(0)    收藏  举报

导航