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:\PythonTest\Homework\class_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:\PythonTest\Homework\class_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:\PythonTest\Homework\class_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
posted @ 2020-06-21 16:55  康帅博丶  阅读(228)  评论(0编辑  收藏  举报