zoukankan      html  css  js  c++  java
  • 01-unittest单元测试框架

    1. unittest框架

    unittestpython中一款内置的测试框架,用来测试代码的运行正确与否。他能够收集测试用例,执行测试用例,查看用例执行结果,还能够添加用例执行的前置条件和后置处理条件。

    unittest中的4大核心概念:

    • TestCase:测试用例,使用这类来编写测试用。它是对一类测试用例进行了封装,例如针对登录操作的测试用例,封装成一个class,针对注册 操作的测试用例,再封装成一个class;且每个class都要继承unittest.TestCase才行。每一条测试用例都是一个函数。
    import unittest
    
    class TestLogin(unittest.TestCase):
        def test_login(self):
            pass
    
    • TestSuit:测试套件,它的本质就是一个容器,里面不存放别的东西,只存放测试用例。
    • TextTestRunner:用例运行器,用来运行测试用例,并呈现出执行结果。
    • Fixture:用例执行的前置和后置条件。
    • TestLoader:用来收集用例。是用来加载TestCaseTestSuite中的。

    2. 简单使用

    1. unittestTestCase使用规范:

    1. 每一个测试类,都要继承unittest.TestCase
    2. 测试类中,每一个函数都要以test_开头,来代表一条测试用例;
    3. 在编写测试用例的时候,需要遵循以下规则:
      1. 要有测试数据;
      2. 要有测试步骤,一般的,测试步骤就是来调用已经完成的功能函数;
      3. 对最后的结果进行断言:只要的最后的结果出现AssertionError的错误,那就是用例失败;
      4. 断言可以使用assert关键字跟表达式来表达,assert如果是True,则表示用例通过,否则是用例失败;
      5. 但是,在使用unittest框架,框架的内部已经为我们封装好了,我们可以直接使用进行用例断言。

    一个简单的用例实例:

    # 功能函数
    def login_check(username=None, password=None):
        """ 登录校验的函数
        :param username: 账号
        :param password: 密码
        :return: dict type
        """
        if username != None and password != None:
            if username == 'python27' and password == 'lemonban':
                return {"code": 0, "msg": "登录成功"}
            else:
                return {"code": 1, "msg": "账号或密码不正确"}
        else:
            return {"code": 2, "msg": "所有的参数不能为空"}
    
    import unittest
    from Homework.class_unittest_study import login
    
    # 这是一个用例类,其中的每一个函数就代表着一个测试用例。
    class LoginTest(unittest.TestCase):
    
        # 正确用户名和密码
        def test_login_cor(self):
            res = login.login_check("python27", "lemonban")
            self.assertEqual(res, {"code": 1, "msg": "登录成功"}, msg="断言失败")
    
        # 用户名密码 都为空
        def test_login_no_pwdAndUsr(self):
            res = login.login_check()
            self.assertEqual(res, {"code": 2, "msg": "所有的参数不能为空"}, msg="断言成功")
    
        # 错误的用户名
        def test_login_wrong_usr(self):
            res = login.login_check("python30", "lemonban")
            self.assertEqual(res, {"code": 1, "msg": "账号或密码不正确"}, msg="断言成功")
    
        # 错误的密码
        def test_login_wrong_pws(self):
            res = login.login_check("python27", "123456")
            self.assertEqual(res, {"code": 1, "msg": "账号或密码不正确"}, msg="断言成功")
    
        # 密码为空
        def test_login_no_pwd(self):
            res = login.login_check(username="python27")
            self.assertEqual(res, {"code": 2, "msg": "所有的参数不能为空"}, msg="断言成功")
    
        # 用户名为空
        def test_login_no_usr(self):
            res = login.login_check(password="lemonban")
            self.assertEqual(res, {"code": 2, "msg": "所有的参数不能为空"}, msg="断言成功")
    
    
    if __name__ == "__main__":
        unittest.main()
    
    

    2. 测试用例前置处理与后置处理

    注意:前置与后置的处理,是需要定义在测试用例类中的,也就是继承了unittest.TestCase的类中、。

    1. setUp()前置方法

    在测试用例类中,定义setUp()方法,当每条测试用例执行的时候,都会先执行setUp()方法,这个方法表示测试用例执行前的前置准备工作,例如测试用例执行的时候需要先访问数据库,或者是创建浏览器实例等等。

    但是,一旦定义了setUp()方法,那么每条测试用例在执行的时候,都会进行相同的前置准备工作,不能够个性化 给每一条测试用例定义不同的前置准备。如果测试用例的前置不一样,那么需要单独重新定义一个新的测试类。

    class LoginTest(unittest.TestCase):
    
        # 每一条测试用例执行前,都会调用这个方法,这个是测试用例执行之前的前置准备工作
        def setUp(self):
            print("测试用例开始执行....")
        
        def test_login_cor(self):
            """这是一条测试用例"""
            pass
    
    2. tearDown()方法

    该方法类似于setUp()方法,只不过这个方法是在测试用例执行完毕之后,才会调用,就相当于每条测试用例执行之后的一个后置处理程序。就比如说要关闭浏览器实例,或者是断开与数据库的链接。

    class LoginTest(unittest.TestCase):
    
        # 每一条测试用例执行前,都会调用这个方法,这个是测试用例执行之前的前置准备工作
        def tearDown(self):
            print("测试用例开始执行....")
        
        def test_login_cor(self):
            """这是一条测试用例"""
            pass
    
    3. setUpClass()

    这个方法也是测试用例的一个前置条件,只不过有一点不同的是,它是一个类属性,只会在测试用例类开始执行的时候,调用一次,并不像setUp()方法是每条测试用例执行的时候都会调用。

    不管这个测试用例类中有几个测试用例,这个方法只会执行一次。

    由于这个方法是一个类属性,所以需要在方法前加装饰器@classmethod来修饰。

    与其相对应的就是tearDownClass()方法。

    class LoginTest(unittest.TestCase):
        
        @classmethod
        def setUpClass(cls):
            print("**** 测试用例类开始执行 ****")
        
        def test_login_cor(self):
            """这是一条测试用例"""
            pass
        
        def test_login_wrong(self):
            """这是一条测试用例"""
            pass
    
    4. tearDownClass()

    测试用例类的后置处理方法, 用于在整个测试用例执行完毕之后调用。

    class LoginTest(unittest.TestCase):
        
        @classmethod
        def setUpClass(cls):
            print("**** 测试用例类开始执行 ****")
        
        @classmethod
        def tearDownClass(cls):
            print("**** 测试用例类执行结束 ****")
        
        def test_login_cor(self):
            """这是一条测试用例"""
            pass
        
        def test_login_wrong(self):
            """这是一条测试用例"""
            pass
    

    3. 用例的收集:unittest.TestSuite()

    这个类,是unittest框架中专门用来收集编写好的各种测试用例的,它可以将测试用例全部收集起来,然后统一执行。

    将测试用例进行收集,这里有两种处理办法:addTest()addTests

    # 1.先将unittest框架和测试用例类引入进来
    import unittest
    
    from Homework.class_unittest_study import test_login
    
    # 实例化一个unittest.TestSuite()套件
    testSuite = unittest.TestSuite()
    
    # 调用addTest方法 或者是addTests()方法,将测试用例添加进来。
    testSuite.addTest(test_login.LoginTest("test_login_cor"))
    
    

    有一点需要注意:addTest()方法一次只能添加一个测试用例,addTests()一次能够添加多个测试用例。但是,添加的方式需要注意:需要在addTest()方法或者addTests()方法中,使用类名(用例方法名)的形式将测试用例添加进来。
    也就是如果你的测试用例类的名字是LoginTest,并且其中的一个用例的方法名是test_login_cor,那么就需要使用LoginTest("test_login_cor")的形式,将这个测试用例添加到测试套件中。

    4. 用例的收集:unittest.TestLoader()

    使用unittest.TestSuite()来收集测试用例,在实际的项目中很不方便,因为不论是addTest()还是addTests(),一旦碰上很多的测试用例,都会变得十分的繁琐,都需要去人工的一条一条的添加测试用例。

    所以,通常用来收集测试用例的,使用unittest.TestLoader()来收集测试用例。

    我们使用unittest.TestLoader()中的discover()来对指定项目路径下的文件,进行测试用例的收集工作。在discover()内部,为我们定义了测试用例的收集规则:

    1. 默认的从以test*.py文件中,获取测试类。也就是说,文件名是以test开头的文件,就是测试类所在的文件;
    2. 找到测试类所在的文件之后,再下一步就是从该文件中获取 测试用例:在测试类所在的文件中,从继承了unittest.TestCase的类中,找以test_开头的方法,认为以test_开头的方法就是测试用例。

    通过上两步的查找,会找到指定的路径中的全部测试用例,并把这些测试用例添加到实例化的unittest.TestLoader()对象中。

    # 1. 在discover方法中,指定搜索目录
    # 2. 在指定的搜索目录中,指定搜索文件
    # 3. 在指定搜索文件中,过滤测试用例:在继承了unittest.TestCase的测试类中,搜索以test_开头的方法,这就是测试用例。
    testLoader = unittest.TestLoader().discover(r"G:PythonTestHomeworkclass_unittest_study")
    print(type(testLoader))
    
    

    5. 运行测试用例集:unittest.TextTestRunner()

    这个是unittest框架自带的一个测试用例运行器,只需要调用就行了

    import unittest
    
    # 将运行结果写入文件
    with open("test_res.txt", mode="w", encoding="utf-8") as f:
        # 1. 实例化unittest.TextTestRunner()
        runner = unittest.TextTestRunner(f)
        # 2. 调用run方法,执行测试用例集
        runner.run(testLoader)
    

    但是,框架自带的这个运行测试用例的方法,它只会显示出一个txt文本格式的结果,在实际项目的时候使用的并不是很多。所以我们 使用别人开发好的一款运行测试用例并生成测试报告的类库:HTMLTestRunnerNew或者是BeautifulReport

    使用HTMLTestRunnerNew生成测试报告

    这个文件需要我们去网上手动下载,下载完成之后,添加到python的安装路径中的Lib下。

    这个类库生成的是一个HTML的文件,所以直接写入文件就可以了:

    import unittest
    
    from Homework.class_unittest_study import test_login
    
    testLoader = unittest.TestLoader().discover(r"G:PythonTestHomeworkclass_unittest_study")
    print(type(testLoader))
    
    from HTMLTestRunnerNew import HTMLTestRunner
    with open("reprot.html", mode="wb") as f:
        runner = HTMLTestRunner(f)
        runner.run(testLoader)
    

    这个类库会生成一个html格式的测试报告,里面会有默认的测试报告的标题,和测试人员,如果想要修改测试报告的名字或者是测试人员的名字,可以修改源码:

    DEFAULT_TITLE = 'py30单元测试报告'
    DEFAULT_DESCRIPTION = ''
    DEFAULT_TESTER='扈先生'
    

    也可以在实例化HTMLTestRunner对象的时候进行传参:

    runner = HTMLTestRunner(f, title="测试报告", tester="Tom")
    
    使用BeautifulReport生成测试报告

    使用pip下载包。

    import unittest
    
    from Homework.class_unittest_study import test_login
    
    testLoader = unittest.TestLoader().discover(r"G:PythonTestHomeworkclass_unittest_study")
    print(type(testLoader))
    
    from BeautifulReport import BeautifulReport
    br = BeautifulReport(testLoader)
    
    # 需要传入测试报告的标题,如果为None,则有默认的标题,也可以传入测试报告的文件路径,同样,如果不传,也有默认的文件名
    br.report("py30测试报告")
    

    BeautifulReport生成的测试报告,默认的是不会显示用例描述的。如果想要在测试报告中显示出来用例的描述,那么需要在测试函数中添加:self.__dict__['_testMethodDoc'] = "用例描述"

    并且,要把这句代码添加到测试函数中靠前的位置,最起码要在最后的断言前面,否则当永陵失败的时候,测试报告中这条失败的测试用例描述依然时空。

    6. 数据驱动思想执行测试用例:ddt框架

    在进行实际的自动化测试时,如果遇到单一流程,只是数据不同的测试时,单纯的使用unittest框架进行编写测试用例,再执行测试用例,这样会浪费大量的时间,严重影响效率。这个时候,就可以使用数据驱动测试的思想来实现测试工作。

    数据驱动测试,这是一种思维方式,也可以理解成将测试数据进行参数化,传递给相同的一个测试流程。

    python中,使用ddt模块来实现数据驱动测试的思维。

    ddt模块的使用很简单,都是一个固定的模板:

    1. 首先,引入ddt框架:import ddt
    2. 之后,在测试类前,添加装饰器@ddt.ddt
    3. 在测试函数前面,添加装饰器:@ddt.data(),并将测试数据传递给这个装饰器。这里最好使用一个列表来存储测试数据,然后使用拆包的方式传递参数;
    4. 并且在测试函数中,添加一个参数,来接收测试数据
    import unittest
    import ddt
    
    login_info = [
        {"username": "python27", "password": "lemonban", "except": {"code": 0, "msg": "登录成功"}},
        {"username": None, "password": None, "except": {"code": 2, "msg": "所有的参数不能为空"}},
        {"username": "python30", "password": "lemonban", "except": {"code": 1, "msg": "账号或密码不正确"}}
    ]
    
    @ddt.ddt
    class LoginTest(unittest.TestCase):
    
        @ddt.data(*login_info)
        def test_login_cor(self, case):
            res = login.login_check(case["username"], case["password"])
            self.assertEqual(res, case["except"], msg="断言失败")
    

    这样一来,ddt框架会根据你的测试数据,来自动为每一条测试数据生成一条测试用例,就上述例子来说,会直接生成3条测试用例。

    ddt的使用是一个固定的模板,十分简单。

    2. unittest框架中常用的断言类型

            方法                    检查
        assertEqual(a, b)           a == b      
        assertNotEqual(a, b)        a != b      
        assertTrue(x)               bool(x) is True      
        assertFalse(x)              bool(x) is False      
        assertIsNone(x)             x is None     
        assertIsNotNone(x)          x is not None   
        assertIn(a, b)              a in b    
        assertNotIn(a, b)           a not in b
    
  • 相关阅读:
    Best Time to Buy and Sell Stock III
    Valid Palindrome
    Longest Substring Without Repeating Characters
    Copy List with Random Pointer
    Add Two Numbers
    Recover Binary Search Tree
    Anagrams
    ZigZag Conversion
    Merge k Sorted Lists
    Distinct Subsequences
  • 原文地址:https://www.cnblogs.com/Pilaoban/p/13173051.html
Copyright © 2011-2022 走看看