zoukankan      html  css  js  c++  java
  • Python中的单元测试模块Unittest快速入门

    前言

    为什么需要单元测试?

    如果没有单元测试,我们会遇到这种情况:已有的健康运行的代码在经过改动之后,我们无法得知改动之后是否引入了Bug。如果有单元测试的话,只要单元测试全部通过,我们就可以保证没有Bug被引入。因此,单元测试是保证软件工程质量的一个很重要的方面。

    Python中的单元测试

    Python最强大的地方在于,开发效率高,并且有丰富的Package,避免重复造轮子。那么Python中的Unittest模块有很丰富的功能提供给我们调用:完整的测试框架,丰富的拓展,比如我们可以设置测试之前的一些初始化工作,比如链接数据库等,规划测试集中有哪些测试用例需要跳过,以及跳过的原因。

    Unittest中几个类(Class)的基本概念

    TestCase 是我们要写的具体的测试用例
    TestSuite 多个测试用例集合在一起,中文翻译过来叫测试套件,其实就是测试集。
    TestLoader是用来加载TestCase到TestSuite中的(更通俗一点,就是用来把符合我们定义的条件的测试用例组合起来,成为一个测试集),一般会以参数的形式传进去一些条件,比如收集某个目录下所有的test case组成新的测试集。
    TestRunner是来执行测试用例的,测试的结果会保存到TestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息

    一个简单的测试例子

    >>> class MyTest(unittest.TestCase):
            #Run before whole testcase set execution, decorator classmethod is essential
        @classmethod
        def setUpClass(self):
            print("UnitTest Begin...")
            #Run after whole testcase set execution, decorator classmethod is essential
        @classmethod
        def tearDownClass(self):
            print("UnitTest End...")
            #Run before each test case execution 
        def setUp(self):
            print("Begin...")
            #Run after each test case execution
        def tearDown(self):
            print("End...")
        def test_1(self):
            self.assertEqual(1,1)
        def test_2(self):
            self.assertEqual(1,2)
    
            
    >>> if __name__ == '__main__':unittest.main()
    
    UnitTest Begin...
    Begin...
    End...
    .Begin...
    End...
    FUnitTest End...
    
    ======================================================================
    FAIL: test_2 (__main__.MyTest)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "<pyshell#41>", line 15, in test_2
    AssertionError: 1 != 2
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.097s
    
    FAILED (failures=1)

    在这个例子中,有几个函数要注意:

    setUp()和tearDown():每个test case执行之前和执行之后要运行的操作:我们可以在这里定义测试的准备工作,比如链接数据库,web登录等等。

    用装饰器classmethod装饰的setUpClass()和tearDownClass(): 跑类中定义的所有test cases之前和之后,需要执行的操作。

    test_1()和test_2(),具体的测试用例,一定要以test为开头,因为unittest框架中,定义为,如果TestCase类中以test为开头的函数,将会作为具体的testcase收录进要执行的测试集里。

    self.assertEqual(),是TestCase类中的断言函数,用来做判断的,用以判断该条测试用例是否通过。通过名字我们可以看出,这个函数的意思是判断两个值是否相等,如果相等,则用例通过,如果不等,则用例不通过。类似的,断言函数还有很多:有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回

     

    三种常见测试写法

    第一种: 搜索该模块下所有以test开头的测试用例方法,并自动执行它们

    #执行测试用例方案一如下:
    #unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行它们。
    
    import unittest
    
    #定义测试类,父类为unittest.TestCase。
    #可继承unittest.TestCase的方法,如setUp和tearDown方法,不过此方法可以在子类重写,覆盖父类方法。
    #可继承unittest.TestCase的各种断言方法。
    class Test(unittest.TestCase): 
        
        def setUp(self):
            self.number=raw_input('Enter a number:')
            self.number=int(self.number)
    
    #定义测试用例,以“test_”开头命名的方法
    #可使用unittest.TestCase类下面的各种断言方法用于对测试结果的判断
        def test_case1(self):
            print self.number
            self.assertEqual(self.number,10,msg='Your input is not 10')
            
        def test_case2(self):
            print self.number
            self.assertEqual(self.number,20,msg='Your input is not 20')
    
        @unittest.skip('暂时跳过用例3的测试')
        def test_case3(self):
            print self.number
            self.assertEqual(self.number,30,msg='Your input is not 30')
    
    
        def tearDown(self):
            print 'Test over'
            
    #如果直接运行该文件(__name__值为__main__),则执行以下语句,常用于测试脚本是否能够正常运行
    if __name__=='__main__':
    #执行顺序是命名顺序:先执行test_case1,再执行test_case2
        unittest.main()

    第二种: 构造测试集,实例化test suite(即TestRunner类), 运行test suite中所有的测试用例。

    '''
    执行测试用例方案二如下:
    先构造测试集
    实例化测试套件
    '''
        suite=unittest.TestSuite()
    #将测试用例加载到测试套件中。
    #执行顺序是安装加载顺序:先执行test_case2,再执行test_case1
        suite.addTest(Test('test_case2'))
        suite.addTest(Test('test_case1'))
    #执行测试用例
    #实例化TextTestRunner类
        runner=unittest.TextTestRunner()
    #使用run()方法运行测试套件(即运行测试套件中的所有用例)
        runner.run(suite)
        

    第三种:通过收集指定目录下的目标测试用例,构造测试集再执行

    #构造测试集(简化了方案二中先要创建测试套件然后再依次加载测试用例)
    #执行顺序同方案一:执行顺序是命名顺序:先执行test_case1,再执行test_case2
        test_dir = './'
        discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
    #执行测试用例
    #实例化TextTestRunner类
        runner=unittest.TextTestRunner()
    #使用run()方法运行测试套件(即运行测试套件中的所有用例)
        runner.run(discover)   

    如何生成HTML和XML格式的测试报告

    上面我们得到的测试结果是文本格式的,可读性不好,并且也无法直接用来作后续的测试结果数据处理,比如测试结果的分类统计等等。

    那么有两种方式可供我们选择:HTML和XML

    HTML格式的报告,就是网页格式,可读性会比较好。XML格式的报告,则比较方便作后续的数据处理。

    需要注意的是,我们需要安装额外的package,即HtmlTestRunner和xmlrunner。

    在配置好Pip的前提下,可以通过以下命令安装:

    pip install html-testrunner
    
    pip instll xmlrunner

    如果没有配置好pip或者用pip安装失败,则需要用以下方式安装(xmlrunner同理):

    1. 下载HTMLTestRunner.py文件:地址http://tungwaiyip.info/software/HTMLTestRunner.html
    2. 将该文件保存在python安装路径下的lib文件夹中。在文件中能import HTMLTestRunner成功,即配置成功。
     
    以生成HTML报告为例:
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import unittest
    import HtmlTestRunner
    
    class TestStringMethods(unittest.TestCase):
        def test_upper(self):
            self.assertEqual('foo'.upper(),'FOO')
        def test_isupper(self):
            self.assertFalse('Foo'.isupper())
        def test_split(self):
            s = 'hello world'
            self.assertEqual(s.split(),['hello','world'])
            with self.assertRaises(TypeError):
                s.split(2)
    
    if __name__ == '__main__':
        suite = unittest.TestSuite()
        suite.addTest(TestStringMethods('test_upper'))
        suite.addTest(TestStringMethods('test_isupper'))
        suite.addTest(TestStringMethods('test_split'))
        runner = HtmlTestRunner.HTMLTestRunner(output='MyUnitTest')
        runner.run(suite)

    最终我们会得到一个可读性比较好的网页报告。

    XML报告:

     有时我们需要得到格式化数据的测试报告,此时XML格式就要比HTML格式好的多。

    因为XML格式的test result容易被读取和数据处理。

    示例代码如下:

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import unittest
    import xmlrunner
    
    class TestStringMethods(unittest.TestCase):
        def test_upper(self):
            self.assertEqual('foo'.upper(),'FOO')
        def test_isupper(self):
            self.assertFalse('Foo'.isupper())
        def test_split(self):
            s = 'hello world'
            self.assertEqual(s.split(),['hello','world'])
            with self.assertRaises(TypeError):
                s.split(2)
    
    if __name__ == '__main__':
        suite = unittest.TestSuite()
        suite.addTest(TestStringMethods('test_upper'))
        suite.addTest(TestStringMethods('test_isupper'))
        suite.addTest(TestStringMethods('test_split'))
        #fp = open('result.html','w')
        runner = xmlrunner.XMLTestRunner(output='MyUnitTest')
        #runner = HtmlTestRunner.HTMLTestRunner(stream=fp,output='MyUnitTest')
        runner.run(suite)

    得到的结果是这样的:

    <?xml version="1.0"?>
    
    -<testsuite time="0.000" tests="3" name="TestStringMethods-20181115000346" failures="0" errors="0">
    
    <testcase time="0.000" name="test_upper" classname="TestStringMethods"/>
    
    <testcase time="0.000" name="test_isupper" classname="TestStringMethods"/>
    
    <testcase time="0.000" name="test_split" classname="TestStringMethods"/>
    
    
    -<system-out>
    
    <![CDATA[]]>
    
    </system-out>
    
    
    -<system-err>
    
    <![CDATA[]]>
    
    </system-err>
    
    </testsuite>

    几个利用unittest做测试的实际例子

    百度搜索测试用例

    from selenium import webdriver
    import unittest, time
    
    class BaiduTest(unittest.TestCase):
        def setUp(self):
            self.driver = webdriver.Firefox()
            self.driver.implicitly_wait(30) #隐性等待时间为30秒
            self.base_url = "https://www.baidu.com"
        
        def test_baidu(self):
            driver = self.driver
            driver.get(self.base_url + "/")
            driver.find_element_by_id("kw").clear()
            driver.find_element_by_id("kw").send_keys("unittest")
            driver.find_element_by_id("su").click()
            time.sleep(3)
            title=driver.title
            self.assertEqual(title, u"unittest_百度搜索") 
    
        def tearDown(self):
            self.driver.quit()
    
    if __name__ == "__main__":
        unittest.main()

    有道翻译测试用例

    from selenium import webdriver
    import unittest, time
    
    class YoudaoTest(unittest.TestCase):
        def setUp(self):
            self.driver = webdriver.Firefox()
            self.driver.implicitly_wait(30) #隐性等待时间为30秒
            self.base_url = "http://www.youdao.com"
        
        def test_youdao(self):
            driver = self.driver
            driver.get(self.base_url + "/")
            driver.find_element_by_id("translateContent").clear()
            driver.find_element_by_id("translateContent").send_keys(u"你好")
            driver.find_element_by_id("translateContent").submit()
            time.sleep(3)
            page_source=driver.page_source
            self.assertIn( "hello",page_source) 
    
        def tearDown(self):
            self.driver.quit()
    
    if __name__ == "__main__":
        unittest.main()

    web测试用例:通过测试套件TestSuite来组装多个测试用例。

    import unittest
    from test_case import test_baidu
    from test_case import test_youdao
    
    #构造测试集
    suite = unittest.TestSuite()
    suite.addTest(test_baidu.BaiduTest('test_baidu'))
    suite.addTest(test_youdao.YoudaoTest('test_youdao'))
    
    if __name__=='__main__':
        #执行测试
        runner = unittest.TextTestRunner()
        runner.run(suite)

    参考链接:

    1. unittest单元测试框架总结

    2. unittest — Unit testing framework https://docs.python.org/3/library/unittest.html

    3. Python单元测试unittest

    4. Python3 unittest断言详解

    5. Python3 unittest单元测试

    6. Python HTMLTestRunner 学习

  • 相关阅读:
    Model
    暑假集训-计算几何
    暑假集训-字符串
    将博客搬至CSDN
    codeforces #519 A A. Multiplication Table (暴力)
    bc #54 A problem of sorting
    vimrc备份
    codeforces # 317 div 2 B. Order Book(模拟)
    codeforces #317 div2 A. Arrays (水)
    bc #52 div 2 A ||hdoj5417 Victor and Machine (模拟)
  • 原文地址:https://www.cnblogs.com/ArsenalfanInECNU/p/9946613.html
Copyright © 2011-2022 走看看