美文网首页
2018-08-07(16)单元测试(下)

2018-08-07(16)单元测试(下)

作者: 棕色试剂瓶 | 来源:发表于2018-08-07 19:06 被阅读0次

    Python基础语法(16)

    单元测试

    测试用例的核心工作原理 : test case(测试用例); test suite(测试套件), test runner(测试运行器); test fixture(测试装备)

    • TestCase:

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

    • TestSuite

      就是多个测试用例的集合,而且TestSuit之间可以互相嵌套。

      • TestLoader用来加载TestCase到TestSuite中,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuit实例。
    • TextTestRunner是用来执行测试用例的,其中的run(test)会执行TestSuit/TestCase中的run(result)方法。

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

    • 使用fixture来堆测试用例环境进行搭建和摧毁。

    代码实例

    test_func.py

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n30" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

    tese_func.py

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n33" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import unittest # 导入unittest模块
    from week4.unit1.test_func import *

    class TestFunc(unittest.TestCase): # 测试用例
    '''test test_func.py'''

    每个测试方法均以test开头,否则是不被unittest识别的。

    def test_add(self):
    '''test method add(a,b)'''
    self.assertEqual(3,add(1,2))
    self.assertNotEqual(2,add(2,2))
    def test_minus(self):
    '''test method minus(a,b)'''
    self.assertEqual(1,minus(3,2))
    def test_multi(self):
    '''test method multi(a,b)'''
    self.assertEqual(6,multi(2,3))
    def test_divide(self):
    '''test method divide(a,b)'''
    self.assertEqual(2,divide(6,3))
    self.assertEqual(2.5, divide(5,2))
    if name == "main":
    unittest.main()
    </pre>

    代码中,TestFunc就是一个测试用例,测试用例每有一个test开头的方法,再load的时候便会生成一个TestCase实例。

    之后我们通过TestLoader加载TestCase到TestSuite,用TextTestRunner来运行TestSuite。运行结果保存再TestTestResult中,我们通过命令行或者unittest.main()执行时,main会调用到TextTestRunner中的run来执行,

    或者我们可以通过直接使用TextTestRunner来执行用例。

    再Runner执行时,默认将执行结果说出到控制台,但是我们可以设置输出到其它文件,再文件中查看结果。(HTMLTestRunner,通过它可以将结果输出到HTML中,生成较为漂亮的报告)

    TestSuite

    通过该方法来控制用例的执行,添加到TestSuite中的case会按照添加的顺序执行。

    还可以再Test Suite中添加多个测试文件,

    代码实例:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n51" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import unittest
    from test_func import TestFunc

    if name == "main":
    suite = unittest.TestSuite()
    tests = [TestFunc("test_add), TestFunc("test_minus"), TestFunc("test_divide")]
    suite.addTests(tests)

    runner = unittest.TextTestRunner(verbosity = 2)
    runner.run(suite)</pre>

    通过下面的方法也可以传入测试用例

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n54" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># 添加单个TestCase
    suite.addTest(TestCalculateFunc("test_multi")) # 注意,是addTest,不是addTests,留神s的区别

    通过使用loadTestFromName() 括号内传入"模块名.TestCase名"

    suite.addTests(unittest.TestLoader().loadTestsFromName("test_func.TestFunc")) # 添加单个
    suite.addTests(unittest.TestLoader().loadTestsFromNames([test_func.TestFunc])) # 添加多,注意s和中括号的差别

    通过使用loadTestsFromTestCase() 传入TestCase

    suite.addTests(unittest.TestLoader().loadTestFromTestCase(TestFunc))
    runner = unittes.TextTestRunner(verbosity = 2) # verbosity = 1 是打印的报告不如2的详细。
    runner.run(suite)</pre>

    注意: 用TestLoader无法对case进行排序

    将测试结果写入文件

    可以将测试结果写入文件,生成测试报告,对模块质量提供依据。

    代码实例:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n65" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">if name == "main":
    suit = unittest.TestSuite()
    suite.addTests(unittest.TestLoader().loadTestFromTestCase(TestCalculateFunc))
    with open("UnittestTestReport.txt","a") as f:
    runner = unittest.TextTestRunner(stream = f, verbosity = 2) # 将测试报告写入文件,可以设置verbosity
    runner.run(suite)</pre>

    unittest中的参数化

    首先需要安装nose_parameterized模块 pip instuall nose_parameterized

    方法模块:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n73" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def login(username,password):
    if username == "Eric" and password == "123456":
    return True
    else:
    return False</pre>

    测试套件模块:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n76" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from parameterized import parameterized

    class TestCalculateFunc(unittest.TestCase):
    '''test func.py'''

    num = 1

    @parameterized.expand([
    ["eric", "123456", True], # 可以是list,也可以是元组
    ["", "123456", True],
    ["eric", "12345", True],
    ["lingling", "123456"]
    ])</pre>

    测试用例模块

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n79" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def test_login(self, username, password, flag):

    这里的参数对应上述列表里的元素,运行时会遍历上述二维列表

    ''' 登录 '''
    res = login(username,password)
    self.assertEqual(res,flag)</pre>

    setUp() , tearDown()

    setUp() 和tearDown()两个方法(本质上是重写了TestCase的这两个方法),

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

    代码实例:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n89" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def setUp(self):
    print("do something before test.Prepare environment")

    def tearDown(self):
    print("do something after test.Clean up")</pre>

    这两个方法要写在测试用例里面。如果想要在每个case执行时只各自执行一次,为了达到这样的效果,我们可以把这两个方法设置为类方法。

    setUpClass() 和 tearDownClass()再给它们加上类修饰符修饰。

    代码实例:

    <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n96" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class TestCalculateFunc(unittest.TestCase):
    @classmethod
    def setUpClass(cls): # 注意,此时为setUpClass(), 不是setUp()
    print("do something before test.Prepare environment")

    @classmethod
    def tearDownClass(cls):
    print("do something after test.Clean up")
    """Test calculate_func.py"""</pre>

    跳过case

    1. skip装饰器

      <pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n104" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">@unittest.skip("I don't want to run this case")
      def test_divides(self):
      ···</pre>

      skip装饰器一共有三个:

      1. unittest.skip(reason)

      2. unittest.skiplf(condition,reason)

      3. unittest.skipUnless(condition,reason)

      skip无条件跳过

      skipif是当condition为True时跳过(只有当条件满足时才跳过)

      skipUnless是当condition为False时跳过(只有当条件满足时才不跳过)

    HTMLTestRunner ——输出漂亮的报告

    通过下载报告库即可使用

    HTMLTestRunner是一个第三方的unittest HTML报告库,首先我们下载HTMLTestRunner.py,并放到当前目录下,或者你的’C:\Python27\Lib’下,就可以导入运行了。

    下载地址:

    官方原版

    修改版

    [Python3.x版本

    总结

    • unittest是python自带的单元测试框架,以后写单元测试时可以拿来用。

    • unittest的流程:

      要被测试的方法==>测试用例==》测试套装==》执行

    • 当class继承unittest.TestCase后就会变成一个TestCase,其中以test开头的方法再load时会被加载为一个真正的TestCase

    • verbosity可以控制测试报告输出的样式,1为一般报告,2为详细报告

    • 可以通过addTest和addTests向suite中添加case或suite,可以使用TestLoader的loadTestForm()方法

    • 可以使用setUp(),tearDown(), setUpClass(), tearDownClass()来进行用例执行前的环境布置和执行后的环境清理。

    • 可以使用skip,skipIf, skipUnless装饰器跳过某个case。或者使用TestCase.skipTest()

    • 再输出方法unittest.TextTestRunner(verbosity=2)中添加stream可以将报告输出到文件。TextTestRunner输出txt报告,HTMLTestRunner输出html报告。

    相关文章

      网友评论

          本文标题:2018-08-07(16)单元测试(下)

          本文链接:https://www.haomeiwen.com/subject/gnoavftx.html