zoukankan      html  css  js  c++  java
  • python单元测试之unittest框架使用总结

    一、什么是单元测试

    单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

    比如对于函数abs(),我们可以编写的测试用例为:

    (1)输入正数,比如1、1.2、0.99,期待返回值与输入相同

    (2)输入复数,比如-1、-1.2、-0.99,期待返回值与输入相反

    (3)输入0,期待返回0

    (4)输入非数值类型,比如None、[]、{}、期待抛出TypeError

    把上面这些测试用例放到一个测试模块里,就是一个完整的单元测试

    二、unittest工作原理

    unittest中最核心的四部分是:TestCase,TestSuite,TestRunner,TestFixture

    (1)一个TestCase的实例就是一个测试用例。测试用例就是指一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。

    (2)而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。

    (3)TestLoader是用来加载TestCase到TestSuite中的。

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

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

    综上,整个流程就是首先要写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中。

    三、下面举两个实例,来看看unittest如何测试一个简单的函数

    (1)编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问例如

    >>> d = Dict(a=1, b=2)
    >>> d['a']
    1
    >>> d.a
    1

    mydict.py代码如下:

    class Dict(dict):
    def __init__(self, **kw):
    super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
    try:
    return self[key]
    except KeyError:
    raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
    self[key] = value


    用于测试的文件mydict_test.py代码如下:

    import unittest
    from mydict import Dict


    class TestDict(unittest.TestCase):
    def test_init(self):
    d = Dict(a=1, b='test')
    self.assertEqual(d.a, 1) # 判断d.a是否等于1
    self.assertEqual(d.b, 'test') # 判断d.b是否等于test
    self.assertTrue(isinstance(d, dict)) # 判断d是否是dict类型

    def test_key(self):
    d = Dict()
    d['key'] = 'value'
    self.assertEqual(d.key, 'value')

    def test_attr(self):
    d = Dict()
    d.key = 'value'
    self.assertTrue('key' in d)
    self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
    d = Dict()
    with self.assertRaises(KeyError): # 通过d['empty']访问不存在的key时,断言会抛出keyerror
    value = d['empty']

    def test_attrerror(self):
    d = Dict()
    with self.assertRaises(AttributeError): # 通过d.empty访问不存在的key时,我们期待抛出AttributeError
    value = d.empty


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


    直接把mydict_test.py当普通的Python脚本运行即可

    输出:

    .....
    ----------------------------------------------------------------------
    Ran 5 tests in 0.000s

    OK


    (2)测一个简单的加减乘除接口

    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

    test_mathfunc.py文件代码如下:

    import unittest
    from mathfunc import *


    class TestMathFunc(unittest.TestCase):

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

    def test_minus(self):
    self.assertEqual(1, minus(3, 2))

    def test_multi(self):
    self.assertEqual(6, multi(3, 2))

    def test_divide(self):
    self.assertEqual(2, divide(6, 3))
    self.assertEqual(2.5, divide(5, 2))

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

    输出:

    .F..
    ======================================================================
    FAIL: test_divide (__main__.TestDict)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "D:/pythonWorkspace/test_mathfunc.py", line 20, in test_divide
    self.assertEqual(2.5, divide(5, 2))
    AssertionError: 2.5 != 2

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

    FAILED (failures=1)

    可以看到一共运行了4个测试,失败了1个,并且给出了失败原因,2.5!=2,也就是说我们的divide方法是有问题的。

    关于输出的几点说明:

    1、在第一行给出了每一个用例执行的结果的标识,成功是.,失败是F,出错是E,跳过是S。从上面可以看出,测试的执行跟方法的顺序没有关系,divide方法写在了第4个,但是却在第2个执行。

    2、每个测试方法均以test开头,否则不能被unittest识别

    3、在uniitest.main()中加verbosity参数可以控制输出的错误报告的详细程度,默认是1,如果设为0, 则不输出每一用例的执行结果,即没有上面的结果中的第1行,如果设为2,则输出详细的执行结果,如下所示:

    test_add (__main__.TestMathFunc) ... ok
    test_divide (__main__.TestMathFunc) ... FAIL
    test_minus (__main__.TestMathFunc) ... ok
    test_multi (__main__.TestMathFunc) ... ok

    ======================================================================
    FAIL: test_divide (__main__.TestMathFunc)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "D:/pythonWorkspace/test_mathfunc.py", line 20, in test_divide
    self.assertEqual(2.5, divide(5, 2))
    AssertionError: 2.5 != 2

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

    FAILED (failures=1)


    四、组织TestSuite

    上面的测试用例在执行的时候没有按照顺序执行,如果想要让用例按照你设置的顺序执行就用到了TestSuite。我们添加到TestSuite中的case是会按照添加的顺序执行的。

    现在我们只有一个测试文件,如果有多个测试文件,也可以用TestSuite组织起来。

    继续上面第二加减乘除的例子,现在再新建一个文件,test_suite.py,代码如下:

    # coding=utf-8
    import unittest
    from test_mathfunc import TestMathFunc

    if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    suite.addTests(tests)

    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

    执行结果如下:

    test_add (test_mathfunc.TestMathFunc) ... ok
    test_minus (test_mathfunc.TestMathFunc) ... ok
    test_divide (test_mathfunc.TestMathFunc) ... FAIL

    ======================================================================
    FAIL: test_divide (test_mathfunc.TestMathFunc)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "D:pythonWorkspaceHTMLTest est_mathfunc.py", line 20, in test_divide
    self.assertEqual(2.5, divide(5, 2))
    AssertionError: 2.5 != 2

    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s

    FAILED (failures=1)

    五、将结果输出到文件

    现在我们的测试结果只能输出到控制台,现在我们想将结果输出到文件中以便后续可以查看。

    将test_suite.py进行一点修改,代码如下:

    # coding=utf-8

    import unittest
    from test_mathfunc import TestMathFunc

    if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    suite.addTests(tests)

    with open('UnittestTextReport.txt', 'a') as f:
    runner = unittest.TextTestRunner(stream=f, verbosity=2)
    runner.run(suite)

    运行该文件,就会发现目录下生成了'UnittestTextReport.txt,所有的执行报告均输出到了此文件中。

    六、test fixture的setUp和tearDown

    当遇到要启动一个数据库这种情况时,只想在开始时连接上数据库,在结束时关闭连接。那么可以使用setUp和tearDown函数。

    class TestDict(unittest.TestCase):

    def setUp(self):
    print 'setUp...'

    def tearDown(self):
    print 'tearDown...'


    这两个方法在每个测试方法执行前以及执行后执行一次,setUp用来为测试准备环境,tearDown用来清理环境,以备后续的测试。

    如果想要在所有case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用setUpClass()与tearDownClass(),代码格式如下:

    class TestMathFunc(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
    print "setUp"

    @classmethod
    def tearDownClass(cls):
    print "tearDown"


    七、跳过某个case

    unittest提供了几种方法可以跳过case

    (1)skip装饰器

    代码如下

    # coding=utf-8
    import unittest
    from mathfunc import *

    class TestMathFunc(unittest.TestCase):

    .....

    @unittest.skip("i don't want to run this case.")
    def test_minus(self):
    self.assertEqual(1, minus(3, 2))


    输出:

    test_add (test_mathfunc.TestMathFunc) ... ok
    test_minus (test_mathfunc.TestMathFunc) ... skipped "i don't want to run this case."
    test_divide (test_mathfunc.TestMathFunc) ... FAIL

    ======================================================================
    FAIL: test_divide (test_mathfunc.TestMathFunc)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "D:pythonWorkspaceHTMLTest est_mathfunc.py", line 28, in test_divide
    self.assertEqual(2.5, divide(5, 2))
    AssertionError: 2.5 != 2

    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s

    FAILED (failures=1, skipped=1)

    skip装饰器一共有三个

    unittest,skip(reason):无条件跳过

    unittest.skipIf(condition, reason):当condition为True时跳过

    unittest.skipUnless(condition, reason):当condition为False时跳过

    (2)TestCase.skipTest()方法

    class TestMathFunc(unittest.TestCase):
    ...
    def test_minus(self):
    self.skipTest('do not run this.')
    self.assertEqual(1, minus(3, 2))


    输出:

    test_add (test_mathfunc.TestMathFunc) ... ok
    test_minus (test_mathfunc.TestMathFunc) ... skipped 'do not run this.'
    test_divide (test_mathfunc.TestMathFunc) ... FAIL

    ======================================================================
    FAIL: test_divide (test_mathfunc.TestMathFunc)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "D:pythonWorkspaceHTMLTest est_mathfunc.py", line 20, in test_divide
    self.assertEqual(2.5, divide(5, 2))
    AssertionError: 2.5 != 2

    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s

    FAILED (failures=1, skipped=1)


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

    txt格式的文本执行报告过于简陋,这里我们学习一下借助HTMLTestRunner生成HTML报告。首先需要下载HTMLTestRunner.py,并放到当前目录下,或者python目录下的Lib中,就可以导入运行了。

    下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html

    将test_suite.py代码修改如下:

    # coding=utf-8

    import unittest
    from test_mathfunc import TestMathFunc
    from HTMLTestRunner import HTMLTestRunner


    if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    suite.addTests(tests)

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


    执行后,控制台输出如下:

    ok test_add (test_mathfunc.TestMathFunc)
    F test_divide (test_mathfunc.TestMathFunc)

    Time Elapsed: 0:00:00.001000

    生成的html:

    九、总结

    1、unittest是python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架。

    2、unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。

    3、一个class继承unittest.TestCase即是一个TestCase,其中以 test 开头的方法在load时被加载为一个真正的TestCase。

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

    5、可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。

    6、用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境

    7、我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。

    8、参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。
    ---------------------
    作者:小拳头
    来源:CSDN
    原文:https://blog.csdn.net/xiaoquantouer/article/details/75089200
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    Azure 虚拟机安全加固整理
    AzureARM 使用 powershell 扩容系统磁盘大小
    Azure Linux 云主机使用Root超级用户登录
    Open edX 配置 O365 SMTP
    powershell 根据错误GUID查寻错误详情
    azure 创建redhat镜像帮助
    Azure Powershell blob中指定的vhd创建虚拟机
    Azure Powershell 获取可用镜像 PublisherName,Offer,Skus,Version
    Power BI 连接到 Azure 账单,自动生成报表,可刷新
    Azure powershell 获取 vmSize 可用列表的命令
  • 原文地址:https://www.cnblogs.com/ExMan/p/10165567.html
Copyright © 2011-2022 走看看