01-unittest单元测试框架
1. unittest
框架
unittest
是python
中一款内置的测试框架,用来测试代码的运行正确与否。他能够收集测试用例,执行测试用例,查看用例执行结果,还能够添加用例执行的前置条件和后置处理条件。
unittest
中的4大核心概念:
TestCase
:测试用例,使用这类来编写测试用。它是对一类测试用例进行了封装,例如针对登录操作的测试用例,封装成一个class
,针对注册 操作的测试用例,再封装成一个class
;且每个class
都要继承unittest.TestCase
才行。每一条测试用例都是一个函数。
import unittest
class TestLogin(unittest.TestCase):
def test_login(self):
pass
TestSuit
:测试套件,它的本质就是一个容器,里面不存放别的东西,只存放测试用例。TextTestRunner
:用例运行器,用来运行测试用例,并呈现出执行结果。Fixture
:用例执行的前置和后置条件。TestLoader
:用来收集用例。是用来加载TestCase
到TestSuite
中的。
2. 简单使用
1. unittest
中TestCase
使用规范:
- 每一个测试类,都要继承
unittest.TestCase
; - 测试类中,每一个函数都要以
test_
开头,来代表一条测试用例; - 在编写测试用例的时候,需要遵循以下规则:
- 要有测试数据;
- 要有测试步骤,一般的,测试步骤就是来调用已经完成的功能函数;
- 对最后的结果进行断言:只要的最后的结果出现
AssertionError
的错误,那就是用例失败; - 断言可以使用
assert
关键字跟表达式来表达,assert
如果是True
,则表示用例通过,否则是用例失败; - 但是,在使用
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()
内部,为我们定义了测试用例的收集规则:
- 默认的从以
test*.py
文件中,获取测试类。也就是说,文件名是以test
开头的文件,就是测试类所在的文件; - 找到测试类所在的文件之后,再下一步就是从该文件中获取 测试用例:在测试类所在的文件中,从继承了
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
模块的使用很简单,都是一个固定的模板:
- 首先,引入
ddt
框架:import ddt
; - 之后,在测试类前,添加装饰器
@ddt.ddt
; - 在测试函数前面,添加装饰器:
@ddt.data()
,并将测试数据传递给这个装饰器。这里最好使用一个列表来存储测试数据,然后使用拆包的方式传递参数; - 并且在测试函数中,添加一个参数,来接收测试数据
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