unittest测试框架详解
如果想学习下其他知识,可以去看看下⾯这些⽂章哦!
selenium系列:
接⼝⾃动化系列:
c语言怎么学比较快python基础:
单元测试的定义
1. 什么是单元测试?
单元测试是指,对软件中的最⼩可测试单元在与程序其他部分相隔离的情况下进⾏检查和验证的⼯作,这⾥的最⼩可测试单元通常是指函数或者类,⼀般是开发来做的,按照测试阶段来分,就是单元测试、集成测试、系统测试以及验收测试。
2.为什么要做单元测试?
单元测试之后,才是集成测试,单个单个的功能模块测试通过之后,才能把单个功能模块集成起来做集
成测试,为了从底层发现bug,单元测试时可以减少合成后出现的问题。
越早发现bug越好,这样可以早点发现问题,不然问题累计到后⾯,很可能会因为⼀个做错了⽽导致整个模块甚⾄更⼤范围的推倒重来,对于时间和经费来说,是⾮常浪费的!
对于测试来说,单元测试就是为了执⾏⽤例,输⼊测试数据--》输出测试结果
unittest框架及原理
做过⾃动化测试的同学应该都知道python中的unittest框架,它是python⾃带的⼀套测试框架,学习起来也相对较容易,unittest框架最核⼼的四个概念:
test case:就是我们的测试⽤例,unittest中提供了⼀个基本类TestCase,可以⽤来创建新的测试⽤例,⼀个TestCase的实例就是⼀个测试⽤例;unittest中测试⽤例⽅法都是以test开头的,且执⾏顺序会按照⽅法名的ASCII值排序。
test fixure:测试夹具,⽤于测试⽤例环境的搭建和销毁。即⽤例测试前准备环境的搭建(SetUp前置条件),测试后环境的还原(TearDown后置条件),⽐如测试前需
要登录获取token等就是测试⽤例需要的环境,运⾏完后执⾏下⼀个⽤例前需要还原环境,以免影响下⼀条⽤例的测试结果。
test suite:测试套件,⽤来把需要⼀起执⾏的测试⽤例集中放到⼀块执⾏,相当于⼀个篮⼦。我们可以使⽤TestLoader来加载测试⽤例到测试套件中。
test runner:⽤来执⾏测试⽤例的,并返回测试⽤例的执⾏结果。它还可以⽤图形或者⽂本接⼝,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。
unittest的断⾔
在python基础中,我们有讲过⼀个assert断⾔,使⽤⽅法⽐较简单,即assert 表达式, 提⽰信息,⽽unittest框架中也提供了⼀个⾃带的断⾔⽅式,主要有以下⼏种:⽅法检查
链接下载去水印assertEqual(a, b,msg=None) a ==b
assertNotEqual(a, b) a !=b
assertTrue(x)bool(x) is True
assertFalse(x)Bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x)x is None
assertIsNotNone(x)x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b)isinstance(a,b)
assertNotIsInstance(a, b)not isinstance(a,b)
如果断⾔失败即不通过就会抛出⼀个AssertionError断⾔错误,成功则标识为通过,以上⼏种⽅式都有⼀个共同点,就是都有⼀个msg参数(表中只列了⼀个,其实都有),默认是None,即msg = None,如果指定msg参数的值,则将该信息作为失败的错误信息返回。
TestCase测试⽤例
编写测试⽤例前,我们需要建⼀个测试类继承unittest⾥⾯的TestCase类,继承这个类之后我们才是真
正的使⽤unittest框架去写测试⽤例,编写测试⽤例的步骤如下:
导⼊unittest模块
创建⼀个测试类,并继承unittest.TestCase()
定义测试⽅法,⽅法名必须以test_开头
调⽤unittest.main()⽅法来运⾏测试⽤例,unittest.main()⽅法会搜索该模块下所有以test开头的测试⽤例⽅法,并⾃动执⾏
下⾯以注册功能为例,这个register.py就是注册功能的代码,没有前端界⾯,功能⽐较简单,只是⽅便⽤于演⽰,直接导⼊就可以使⽤。
# register.py
users = [{'username': 'test', 'password': '123456'}]
def register(username, password1, password2):
if not all([username, password1, password2]):
return {"code": 0, "msg": "所有参数不能为空"}
# 注册功能
for user in users:
if username == user['username']:
return {"code": 0, "msg": "该⽤户名已存在!"}
else:
if password1 != password2:
return {"code": 0, "msg": "两次密码输⼊不⼀致!"}
else:
if 6 <= len(username) >= 6 and 6 <= len(password1) <= 18:
users.append({'username': username, 'password': password2})
return {"code": 1, "msg": "注册成功"}
else:
return {"code": 0, "msg": "⽤户名和密码必须在6-18位之间"}
下⾯是编写测试⽤例例⼦:
# test_register.py
import unittest
from register import register # 导⼊被测试的代码
class TestRegister(unittest.TestCase):
"""注册接⼝测试⽤例类"""
def test_register_success(self):
"""注册成功"""python基础知识测试
data = ("mikitest", "miki123", "miki123") # 测试数据
expected = {"code": 1, "msg": "注册成功"} # 预期结果
result = register(*data) # 把测试数据传到被测的代码,接收实际结果
self.assertEqual(expected, result) # 断⾔,预期和实际是否⼀致,⼀致即⽤例通过
def test_username_isnull(self):
"""注册失败-⽤户名为空"""
data = ("", "miki123", "miki123")
expected = {"code": 0, "msg": "所有参数不能为空"}
result = register(*data)
self.assertEqual(expected, result)
def test_username_lt6(self):
"""注册失败-⽤户名⼤于18位"""
data = ("mikitestmikitestmikitest", "miki123", "miki123")
expected = {"code": 0, "msg": "⽤户名和密码必须在6-18位之间!"}
result = register(*data)
self.assertEqual(expected, result) # 这条⽤例应该是不通过的,注册代码bug
def test_pwd1_not_pwd2(self):
"""注册失败-两次密码不⼀致"""
data = ("miki123", "test123", "test321")
expected = {"code": 0, "msg": "两次密码输⼊不⼀致!"}
result = register(*data)
self.assertEqual(expected, result)
# 如果直接运⾏这个⽂件,需要使⽤unittest中的main函数来执⾏测试⽤例
if __name__ == '__main__':
unittest.main()
上⾯传递测试数据处⽤到⼀个*解包,我在python基础中有讲过解包的原理和例⼦,不明⽩的可以往回翻看⼀下,传送门:
测试⽤例运⾏结果如下,⼀共4条⽤例,其中通过3条,不通过1条,不通过的是本⾝注册代码的bug。
Testing started at 21:58 ...
C:\software\ "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --target test_register.TestRegister
Launching unittests with arguments python -m unittest test_register.TestRegister in D:\learn\python_test
{'code': 1, 'msg': '注册成功!'} != {'code': 0, 'msg': '⽤户名和密码必须在6-18位之间!'}
Expected :{'code': 0, 'msg': '⽤户名和密码必须在6-18位之间!'}
Actual :{'code': 1, 'msg': '注册成功!'}
<Click to see difference>
Traceback (most recent call last):
File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equals old(self, first, second, msg)
File "C:\software\python\lib\unittest\case.py", line 839, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\software\python\lib\unittest\case.py", line 1138, in assertDictEqual
self.fail(self._formatMessage(msg, standardMsg))
列表框的style属性File "C:\software\python\lib\unittest\case.py", line 680, in fail
raise self.failureException(msg)
AssertionError: {'code': 0, 'msg': '⽤户名和密码必须在6-18位之间!'} != {'code': 1, 'msg': '注册成功!'}
- {'code': 0, 'msg': '⽤户名和密码必须在6-18位之间!'}
+ {'code': 1, 'msg': '注册成功!'}
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\software\python\lib\unittest\case.py", line 59, in testPartExecutor
yield
File "C:\software\python\lib\unittest\case.py", line 615, in run
testMethod()
File "D:\learn\python24\python_base\day13_task\test_register.py", line 36, in test_username_lt6
self.assertEqual(expected, result)
Ran 4 tests in 0.007s
FAILED (failures=1)
Process finished with exit code 1
Assertion failed
TestFixure测试夹具
unittest的测试夹具有两种使⽤⽅式,⼀种是以测试⽅法为维度的setUp()和tearDown(),⼀种是以测试类为维度的setUpClass()和tearDownClass()。以注册功能为例,但这个注册代码⽐较简单,没有真正需要⽤到测试夹具的地⽅,因此这只是个⽤法演⽰。
# test_register.py
import unittest
from register import register # 导⼊被测试的代码
class TestRegister(unittest.TestCase):
"""注册接⼝测试⽤例类"""
def setUp(self):
# 每条⽤例执⾏之前都会执⾏
print("⽤例{}开始执⾏--".format(self))
def tearDown(self):
# 每条⽤例执⾏之后都会执⾏
print("⽤例{}执⾏结束--".format(self))
@classmethod # 指明这是个类⽅法以类为维度去执⾏的
def setUpClass(cls):
# 整个测试⽤例类中的⽤例执⾏之前,会先执⾏此⽅法
print("-----setup---class-----")
@classmethod
def tearDownClass(cls):
# 整个测试⽤例类中的⽤例执⾏完之后,会执⾏此⽅法
print("-----teardown---class-----")
def test_register_success(self):
"""注册成功"""
data = ("mikitest", "miki123", "miki123") # 测试数据
expected = {"code": 1, "msg": "注册成功!"} # 预期结果
result = register(*data) # 把测试数据传到被测的代码,接收实际结果
self.assertEqual(expected, result) # 断⾔,预期和实际是否⼀致,⼀致即⽤例通过
def test_username_isnull(self):
"""注册失败-⽤户名为空"""
data = ("", "miki123", "miki123")
expected = {"code": 0, "msg": "所有参数不能为空!"}
result = register(*data)
self.assertEqual(expected, result)
# 如果直接运⾏这个⽂件,需要使⽤unittest中的main函数来执⾏测试⽤例
if __name__ == '__main__':
unittest.main()
运⾏结果:
Testing started at 22:19 ...
C:\software\ "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/learn/python/test_register.py
Launching unittests with arguments python -m unittest D:/learn/python/test_register.py in D:\learn\python
-----setup---class-----⽤例test_pwd1_not_pwd2 (test_register.RegisterTestCase)开始执⾏--
⽤例test_pwd1_not_pwd2 (test_register.RegisterTestCase)执⾏结束--
⽤例test_register_success (test_register.RegisterTestCase)开始执⾏--
⽤例test_register_success (test_register.RegisterTestCase)执⾏结束--
⽤例test_username_isnull (test_register.RegisterTestCase)开始执⾏--
⽤例test_username_isnull (test_register.RegisterTestCase)执⾏结束--
⽤例test_username_lt6 (test_register.RegisterTestCase)开始执⾏--
⽤例test_username_lt6 (test_register.RegisterTestCase)执⾏结束--
-----teardown---class-----
Ran 4 tests in 0.003s
OK
Process finished with exit code 0
TestSuite测试套件
unittest.TestSuite()类来表⽰⼀个测试⽤例集,把需要执⾏的⽤例类或模块存到⼀起,常⽤的⽅法如下:
unittest.TestSuite()
addTest():添加单个测试⽤例⽅法
addTest([..]):添加多个测试⽤例⽅法,⽅法名存在⼀个列表
unittest.TestLoader()
loadTestsFromTestCase(测试类名):添加⼀个测试类
loadTestsFromModule(模块名):添加⼀个模块
电容器分为哪几种discover(测试⽤例的所在⽬录):指定⽬录去加载,会⾃动寻这个⽬录下所有符合命名规则的测试⽤例
# run_test.py,与test_register.py、register.py同⼀⽬录下
import unittest
import test_register
# 第⼀步,创建⼀个测试套件
suite = unittest.TestSuite()
# 第⼆步:将测试⽤例,加载到测试套件中
# ⽅式1,添加单条测试⽤例
# case = test_register.TestRegister("test_register_success") # 创建⼀个⽤例对象,注意:通过⽤例类去创建测试⽤例对象的时候,需要传⼊⽤例的⽅法名(字符串类型)
# suite.addTest(case) # 添加⽤例到测试套件中
# ⽅式2,添加多条测试⽤例
# case1 = test_register.TestRegister("test_register_success")
# case2 = test_register.TestRegister("test_username_isnull")
# suite.addTest([case1, case2]) # 添加⽤例到测试套件中
# ⽅式3,添加⼀个测试⽤例类
# loader = unittest.TestLoader() # 创建⼀个加载对象
# suite.addTest(loader.loadTestsFromTestCase(test_register.TestRegister))
# ⽅式4,添加⼀个模块
loader = unittest.TestLoader() # 创建⼀个加载对象
suite.addTest(loader.loadTestsFromModule(test_register))
# ⽅式5,指定测试⽤例的所在的⽬录路径,进⾏加载
# loader = unittest.TestLoader()
# suite.addTest(loader.discover(r"d:\learn\python"))
通常我们使⽤⽅式4、5⽐较多,你可以根据实际情况来运⽤。其中⽅式5,还可以⾃定义匹配规则,默认是会寻⽬录下test*.py⽂件,即所有以test开头命名的py⽂件,⾃定义如下:
loader = unittest.TestLoader()
suite.addTest(loader.discover(start_dir = r"d:\learn\python", pattern="test_case*.py")) # 匹配规则:所有以test_case开头的
TestRunner执⾏⽤例
test runner顾名思义就是⽤来执⾏测试⽤例的,并且可以⽣成相应的测试报告。测试报告有两种展⽰形式,⼀种是text⽂本,⼀种是html格式。
html格式的就是HTMLTestRunner了,HTMLTestRunner 是 Python 标准库的 unittest 框架的⼀个扩展,
它可以⽣成⼀个直观清晰的 HTML 测试报告。使⽤的前提就是要下载 HTMLTestRunner.py,下载完后放在python的安装⽬录下的scripts⽬录下即可。
text⽂本相对于html来说过于简陋,与控制台输出的没有什么区别,也⼏乎没有⼈使⽤,这⾥不作演⽰,使⽤⽅法是⼀样的。我们结合前⾯的测试套件来演⽰⼀下如何⽣成html 格式的测试报告:
# run_test.py,与test_register.py、register.py同⼀⽬录下
import unittest
import test_register
from HTMLTestRunner import HTMLTestRunner
# 创建测试套件
suite = unittest.TestSuite()
# 通过模块加载测试⽤例
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromModule(test_register))
# 创建测试运⾏程序启动器
runner = HTMLTestRunner(stream=open("report.html", "wb"), # 打开⼀个报告⽂件,将句柄传给stream
tester="miki", # 报告中显⽰的测试⼈员
description="注册接⼝测试报告", # 报告中显⽰的描述信息
title="⾃动化测试报告") # 报告的标题
# 使⽤启动器去执⾏测试套件⾥的⽤例
runner.run(suite)
相关参数说明:汇编网
stream:指定输出的⽅式
tester:报告中要显⽰的测试⼈员的名字
description:报告中要显⽰的⾯熟信息
title:测试报告的标题
verbosity :表⽰测试报告信息的详细程度,⼀共三个值,默认是2
0 (静默模式):你只能获得总的测试⽤例数和总的结果,如:总共100个失败10 成功90
1 (默认模式):类似静默模式,只是在每个成功的⽤例前⾯有个. 每个失败的⽤例前⾯有个F
2 (详细模式):测试结果会显⽰每个测试⽤例的所有相关的信息
运⾏完毕,你会发现你的项⽬⽬录下已经⽣成了⼀个report.html⽂件,在浏览器中打开,就可以查看测试报告了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论