为什么要学习unittest
按照测试阶段来划分,可以将测试分为单元测试、集成测试、体系测试和验收测试。单元测试是指对软件中的最小可测试单元在与程序其他部分相隔离的环境下进行检查和验证的工作,通常指函数或者类,一般是开发完成的。
单元测试可以将测试工作前移,及早发现问题,降低解决问题的成本。同时单元测试还可以包管单个模块的功能没有问题,为后续的集成测试提供预备,可以减少合成后的出现的问题。
对于测试来说,单元测试就是实行用例。为了更好的管理用例,我们需要学习Python自带的单元测试框架unittest.
unittest框架及原理
unittest是python自带的一套测试框架,学习起来也相对容易,unittest框架最核心的概念有四个:
(1)test case:测试用例。unittest中提供了一个根本类TestCase,可以用来创建新的测试用例,一个TestCase的示例就是一个测试用例;unittest中测试用例的方法都是以 test开头的,且实行顺序会按照方法名的ASCII值排序。
(2)test fixure:测试夹具。用于测试用例换进的搭建和销毁,即用例测试前环境的大件(SetUp前置条件),测试后环境的恢复(TearDown后置条件)。比如测试前需要登陆获取token等就是测试用例需要的环境,运行完成后实行下一个用例前需要还原环境,以免影响下一条用例的测试结果。
(3)test suit:测试套件。用来把需要一起实行的测试用例几种放到一块实行,相称于一个篮子。我们一般使用 TestLoader来加载测试用例到测试套件中。
(4)test runner:测试运行。用来实行测试用例的,并返回测试用例的实行结果。可以联合图形或者文本接口,把返回的测试结果更形象的展示出来,如 HTMLTestRunner。
unittest断言
断言是测试用例的中很重要的一部分内容,可以用来检查操作是否精确。比如说登入处理,乐成后的页面肯定有类似于用户名称之类的元素,这个时候我们就可以使用断言判断预期结果与现实是否一致,假如符合,就可以以为测试用例通过。
在Python基础中,有一个 assert断言方法,根本使用格式为 assert 表达式,根本信息。在unittest框架中,也提供了一个自带的断言方式,假如断言失败即不通过就会抛出一个 AssertionError断言错误;乐成则标识通过。
以下的断言方法都有一个 msg=None参数(表中只列出了第一个,其实都有),默认返回 None。但是假如指定msg参数的值,则将该信息作为失败的错误信息返回。
TestCase测试用例
编写测试用例前,我们需要创建一个类继承unittest内里的TestCase类,之后我们才气真正的使用unittest框架去编写测试用例.
步调如下:
(1)导入unittest模块
(2)创建一个测试类,继承unittest.TestCase类
(3)界说测试方法,方法名必须以test_开头
(4)调用unittest.main()方法来运行测试用例。unittest.main()方法会搜索该模块下所有以test开头的测试用例和方法,并自动实行。
代码示例
- [/code] [list=1]
- [*] # 注册功能代码
- [*]
- [*] # users列表存储乐成注册的用户
- [*] users = [{'username': 'testing', '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) <= 18 and 6 <= len(password1) <= 18:
- [*] # 追加到users列表
- [*] users.append({'username': username, 'password': password2})
- [*] return {'code': 0, 'msg': '注册乐成.'}
- [*] else:
- [*] return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
- [*]
- [*]
- [*]
- [*]
- [*] import unittest
- [*] from demo import register # 导入被测试代码
- [*]
- [*] class RegisterTest(unittest.TestCase):
- [*] '''注册接口测试类'''
- [*]
- [*] def test_register_success(self):
- [*] '''注册乐成'''
- [*] data = ('palien', 'palien', 'palien') # 测试数据
- [*] result = register(*data) # 测试结果
- [*] expected = {'code': 0, 'msg': '注册乐成.'} # 预期结果
- [*] self.assertEqual(result, expected) # 断言测试结果与预期结果一致
- [*] # pass
- [*]
- [*] def test_username_exist(self):
- [*] '''注册失败-用户名已存在'''
- [*] data = ('testing', '123456', '123456')
- [*] result = register(*data)
- [*] expected = {'code': 0, 'msg': '用户名已存在!'}
- [*] self.assertEqual(result, expected)
- [*]
- [*] def test_username_isnull(self):
- [*] '''注册失败-用户名为空'''
- [*] data = ('', 'palien', 'palien')
- [*] result = register(*data)
- [*] expected = {'code': 0, 'msg': '所有参数不能为空.'}
- [*] self.assertEqual(result, expected)
- [*] # pass
- [*]
- [*] def test_username_lt18(self):
- [*] '''注册失败-用户名长度大于18位'''
- [*] data = ('palienpalienpalienpalien', 'palien', 'palien')
- [*] result = register(*data)
- [*] expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
- [*] self.assertEqual(result, expected)
- [*] # pass
- [*]
- [*] def test_password1_not_password2(self):
- [*] '''注册失败-两次输入密码不一致'''
- [*] data = ('palien', 'palien1', 'palien2')
- [*] result = register(*data)
- [*] expected = {'code': 0, 'msg': '两次密码输入不一致!'}
- [*] self.assertEqual(result, expected)
- [*] # pass
- [*]
- [*] # 假如要直接运行这个测试类,需要使用unittest中的main函数来实行测试用例
- [*] if __name__ == '__main__':
- [*] unittest.main()
- [*]
- [*] # Output
- [*] Windows PowerShell
- [*] 版权所有 (C) Microsoft Corporation。保存所有权利。
- [*]
- [*] 实验新的跨平台 PowerShell https://aka.ms/pscore6
- [*]
- [*] PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
- [*] PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
- [*] .....
- [*] ----------------------------------------------------------------------
- [*] Ran 5 tests in 0.001s
- [*]
- [*] OK
- [*] PS D:\d_02_study\01_git\papers\system\02automation>
- [/list] [size=3]TestFixture测试夹具[/size]
- unittest的测试夹具有两种使用方式,一种是以测试用例的方法为维度的:setUp()和 tearDown();另一种是以测试类为维度的:setUpClass()和 tearDownClass()。
- [b]代码示例:[/b]
- [code]
复制代码
- # users列表存储乐成注册的用户
- users = [{'username': 'testing', '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) <= 18 and 6 <= len(password1) <= 18:
- # 追加到users列表
- users.append({'username': username, 'password': password2})
- return {'code': 0, 'msg': '注册乐成.'}
- else:
- return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
- import unittest
- from demo import register # 导入被测试代码
- class RegisterTest(unittest.TestCase):
- '''注册接口测试类'''
- @classmethod # 指明这是个类方法,以类为维度去实行的
- def setUpClass(cls) -> None:
- '''整个测试用例类中的用例实行之前,会先实行此方法'''
- print('-----setup---class-----')
- @classmethod
- def tearDownClass(cls) -> None:
- '''整个测试用例类中的用例实行完成后,会实行此方法'''
- print('-----teardown---class-----')
- def setUp(self):
- '''每条测试用例实行前都会实行'''
- print('用例{}开始实行...'.format(self))
- def tearDown(self):
- '''每条测试用例实行结束后都会实行'''
- print('用例{}实行结束...'.format(self))
- def test_register_success(self):
- '''注册乐成'''
- data = ('palien', 'palien', 'palien') # 测试数据
- result = register(*data) # 测试结果
- expected = {'code': 0, 'msg': '注册乐成.'} # 预期结果
- self.assertEqual(result, expected) # 断言测试结果与预期结果一致
- # pass
- def test_username_exist(self):
- '''注册失败-用户名已存在'''
- data = ('testing', '123456', '123456')
- result = register(*data)
- expected = {'code': 0, 'msg': '用户名已存在!'}
- self.assertEqual(result, expected)
- def test_username_isnull(self):
- '''注册失败-用户名为空'''
- data = ('', 'palien', 'palien')
- result = register(*data)
- expected = {'code': 0, 'msg': '所有参数不能为空.'}
- self.assertEqual(result, expected)
- # pass
- def test_username_lt18(self):
- '''注册失败-用户名长度大于18位'''
- data = ('palienpalienpalienpalien', 'palien', 'palien')
- result = register(*data)
- expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
- self.assertEqual(result, expected)
- # pass
- def test_password1_not_password2(self):
- '''注册失败-两次输入密码不一致'''
- data = ('palien', 'palien1', 'palien2')
- result = register(*data)
- expected = {'code': 0, 'msg': '两次密码输入不一致!'}
- self.assertEqual(result, expected)
- # pass
- # 假如要直接运行这个测试类,需要使用unittest中的main函数来实行测试用例
- if __name__ == '__main__':
- unittest.main()
- ### Output
- PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
- PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
- -----setup---class-----
- 用例test_password1_not_password2 (__main__.RegisterTest)开始实行...
- 用例test_password1_not_password2 (__main__.RegisterTest)实行结束...
- .用例test_register_success (__main__.RegisterTest)开始实行...
- 用例test_register_success (__main__.RegisterTest)实行结束...
- .用例test_username_exist (__main__.RegisterTest)开始实行...
- 用例test_username_exist (__main__.RegisterTest)实行结束...
- .用例test_username_isnull (__main__.RegisterTest)开始实行...
- 用例test_username_isnull (__main__.RegisterTest)实行结束...
- .用例test_username_lt18 (__main__.RegisterTest)开始实行...
- 用例test_username_lt18 (__main__.RegisterTest)实行结束...
- .-----teardown---class-----
- ----------------------------------------------------------------------
- Ran 5 tests in 0.004s
- OK
- PS D:\d_02_study\01_git\papers\system\02automation>
TestSuit测试套件
unittest.TestSuit()类用来表现一个测试用例集,把需要实行的用例类或模块聚集在一起。
常用的方法:
(1)unittest.TestSuit()
(2)addTest():添加单个测试用例方法
(3)addTest([...]):添加多个测试用例方法,方法名存在一个列表
(4)unittest.TestLoader()
(5)loadTestsFromTestCase(测试类名):添加一个测试类
(6)loadTestsFromMdule(模块名):添加一个模块
(7)discover(测试用例所在的目录):指定目录去加载,会自动寻找这个目录下所有符合定名规则的测试用例
代码示例:
- [/code] [list=1]
- [*] '''
- [*] 以下三个文件必须在同一文件夹下:
- [*] demo.py
- [*] test_demo.py
- [*] run_test.py
- [*] '''
- [*]
- [*] import os
- [*] import unittest
- [*] import test_demo
- [*]
- [*] # 第一步,创建一个测试套件
- [*] suit = unittest.TestSuite()
- [*]
- [*] # 第二步,将测试用例加载到测试套件中
- [*]
- [*] # # 方式一,添加单条测试用例
- [*] # case = test_demo.RegisterTest('test_register_success')
- [*] # '''
- [*] # 创建一个用例对象。
- [*] # 注意:通过用例类去创建测试用例对象的时候,需要传入用例的方法名(字符串类型)
- [*] # 这里不是像调用平常类中的方法那样通过类名.方法名调用,可以理解为unittest框架的特殊之处
- [*] # '''
- [*] # suit.addTest(case) # 添加用例到测试套件中
- [*]
- [*] # # 方式二:添加多条用例
- [*] # case1 = test_demo.RegisterTest('test_username_exist')
- [*] # case2 = test_demo.RegisterTest('test_username_isnull')
- [*] # suit.addTest([case1, case2]) # 添加用例到测试套件中。注意这里使用的是suit.addTest()方法而不是suit.addTests()方法
- [*]
- [*] # # 方式三:添加一个测试用例集
- [*] # loader = unittest.TestLoader() # 创建一个加载对象
- [*] # suit.addTest(loader.loadFromTestCase(test_demo.RegisterTest)) # 通过加载对象从测试类中加载用例到测试套件中
- [*]
- [*] # '''
- [*] # 通产我们使用方式4、5比较多,可以根据现实环境来运用。
- [*] # 其中方式5还可以自界说匹配规则,默认是会寻找目录下的test*.py文件,即所有以test开头定名的py文件。
- [*] # '''
- [*] # # 方式四:添加一个模块(其实就是一个后缀名为.py文件,这里就是test_demo.py文件)
- [*] # loader = unittest.TestLoader() # 创建一个加载对象
- [*] # suit.addTest(loader.loadTestsFromModule(test_demo)) # 通过加载对象从模块中加载测试用例到测试套件中
- [*]
- [*] # 方式五:指定测试用例的所在目录路径,进行加载
- [*] loader = unittest.TestLoader() # 创建一个加载对象
- [*] case_path = os.path.dirname(os.path.abspath(__file__)) # 文件路径
- [*] # print('用例所在的目录路径为:', case_path)
- [*] # suit.addTest(loader.discover(case_path)) # 通过加载对象从用例所在目录加载测试用例到测试套件
- [*] suit.addTest(loader.discover(start_dir=case_path, pattern='test_demo*.py')) # 两个参数:路径和匹配规则
- [/list] [size=3]TestRunner实行用例[/size]
- testRunner用来实行用例,而且可以生成相应的测试陈诉。测试陈诉有两种情势:一种是 text文本;一种是 html格式。
- html格式是 HTMLTestRunner插件辅助生成的,是Python尺度库的unittest框架的一个拓展,可以生成一个清晰直观的html测试陈诉。使用的条件就是需要下载 HTMLTestRunner.py,下载完成后放在python安装目录下的scripts目录下即可。
- text文本相对于html来说过于大略,与输出控制台没有什么区别,也几乎不适用。
- [b]代码示例:[/b]
- [code]
复制代码
- # demo.py,与test_demo.py和run_test.py在同一目录下
- # 导入模块
- import unittest
- import os
- import test_demo
- from HTMLTestReportCN import HTMLTestRunner
- # 用例文件所在目录
- base_path = os.path.dirname(os.path.abspath(__file__))
- # report_path = base_path + 'report.html'
- # 打开陈诉文件
- # 创建测试套件
- suit = unittest.TestSuite()
- # 通过模块加载测试用例
- loader = unittest.TestLoader()
- suit.addTest(loader.discover(start_dir=base_path, pattern='test_demo*.py'))
- # 创建测试运行程序启动器
- runner = HTMLTestRunner(
- stream=open('report.html', 'w', encoding='utf-8'), # 打开一个陈诉文件,并将句柄传给stream
- tester='palien', # 陈诉中显示的测试人员
- description='注册接口测试陈诉', # 陈诉中显示的描述信息
- title='自动化测试陈诉' # 测试陈诉标题
- )
- # 使用启动器去实行测试套件内里的测试用例
- runner.run(suit)
相干参数说明:
stream:指定输出方式
tester:陈诉中要显示的测试人员的名字
description:陈诉中要显示的描述信息
title:测试陈诉的标题
verbosity:表现测试陈诉信息的具体程度,一共三个值,默以为2
0(静默模式):只能获得总的测试用例书和总的结果,如:总共100个,失败90
1(默认模式):类似静默模式,只是在在每个乐成的用例眼前有个. 每个失败的用例前面有个F
2(具体模式):测试结果会显示每个测试用例的所有相干信息
运行完毕,在项目目录下面会生成一个report.html文件,在浏览器中打开,就可以看到测试陈诉了。
问题记载
在学习的过程中遇到了一些问题,记载一下。
HTMLTestRunner下载
经验证,以下两个文件就支持生成上面截图的陈诉。
报错 TypeError: a bytes-like object is required, not 'str'解决
这是打开文件的方式不对造成的,附上问题解决博客:https://blog.csdn.net/Teresa_lqs/article/details/126250505?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-126250505-blog-116235034.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-126250505-blog-116235034.pc_relevant_default&utm_relevant_index=1
中文陈诉乱码问题
问题如图:
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,固然不是什么很值钱的东西,假如你用得到的话可以直接拿走:
这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰巨的旅程,希望也能资助到你!凡事要趁早,特别是技术行业,肯定要提拔技术功底。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |