单元测试
1.什么是单元、单元测试
单元: 指的是函数或者是类,测试的最小单元
单元测试:就是测试代码里面的函数或者是类,是不是按照预先定义好的去执行
2.为什么要做单元测试?
好处:投入小,收益大,能够精准的、更早的发现问题
3.单元测试与我有什么关系?
Python语言很难测试java的单元
单元测试一般是由开发,测开人员写的
但是自动化测试可以做 集成测试、系统测试、验收测试
4.学单元测试框架干嘛呢?(每一种语言都会自带一个单元测试框架)
单元测试框架可以用于集成测试、系统测试
5.编写被测对象(函数),后面根据这个函数再编写测试用例
♥单元测试框架:unittest
♥unittest的注意规范:模块名称 test_.....
类名 Test....
测试用例的方法名称 test_....
测试用例类TestLogin(unittest.TestCase) 不要把继承的父类丢掉
unittest 中,建立的 python file的名字必须是以test开头,比如:test_login.py
(1)、编写测试登录功能(函数)
"""测试登录功能(函数)""" def login(username = None,password = None): """ :param username: 登录校验的用户名 :param password: 登录校验的密码 :return:dict type """ if username != None and password != None: if username == "polo" and password == "123456": return {"code":0,"msg":"登录成功!"} else: return {"code":1,"msg":"登录失败,用户名或密码错误!"} else: return {"code":1,"msg":"登录失败,用户名或密码为空!"} #用户名或密码为空 if __name__ =='__main__': print(login()) #密码错误 if __name__ =='__main__': print(login('polo','123')) #用户名、密码都正确 if __name__ =='__main__': print(login('polo','123456')) ''' {'code': 1, 'msg': '登录失败,用户名或密码为空!'} {'code': 1, 'msg': '登录失败,用户名或密码错误!'} {'code': 0, 'msg': '登录成功!'} '''
(2)、测试用例---TestCase (根据被测函数,设计测试用例)---------测试用例的方法是实例方法,不然用不了self.assertTrue
测试用例的类名,必须是Test开头,比如:TestLogin
断言:assertTrue(表达式) 重点用好这个,
assertEqual(两个参数)--------assertEqual(4,3),不相等,报错
assertGreater(两个参数)------assertGreater(4,3)4>3是正确的,就是等价于assertTrue(4>3)
由上可知,assertTrue(表达式)能够包含别的用法,所以重点用好assertTrue(表达式)。熟悉了用法再去拓展别的用法。
(assert断言的源码里已经进行了判断,用了try,用起来方便,比外面再进行if判断,写很多分支方便)
♥运行测试用例的注意事项
①写完代码换行,在空白行处运行
②运行测试用例,在setting→搜索unittest,将Default test runner 设置为Unittests
举例1:两个测试用例都通过
#导入单元测试框架unittest import unittest """测试登录功能(函数)""" def login(username = None,password = None): """ :param username: 登录校验的用户名 :param password: 登录校验的密码 :return:dict type """ if username != None and password != None: if username == "polo" and password == "123456": return {"code":0,"msg":"登录成功!"} else: return {"code":1,"msg":"登录失败,用户名或密码错误!"} else: return {"code":1,"msg":"登录失败,用户名或密码为空!"} #设计登录测试用例类 class TestLogin(unittest.TestCase):#继承unittest.TestCase def test_login_success(self): '''登录成功''' username = "polo" password = "123456" expected_reponse = {"code":0,"msg":"登录成功!"} #实际结果:调用login函数 actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用 #判断预期结果跟实际结果是否一样 断言 self.assertTrue(expected_reponse == actual_reponse) def test_login_error(self): '''登录失败''' username = '' password = '' expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"} actual_reponse = login(username, password) self.assertTrue(expected_reponse == actual_reponse)
'''
Ran 2 tests in 0.002s
OK
'''
♥上面的类直接运行测试用例,但是没有实例化对象------->因为unittest内部已经做了设置初始化,直接使用run 'unittest......'即可
举例2:两个测试用例,有一个没通过(下面代码的黄底做了改动,预期结果跟实际结果不相等,异常)
#导入单元测试框架unittest import unittest """测试登录功能(函数)""" def login(username = None,password = None): """ :param username: 登录校验的用户名 :param password: 登录校验的密码 :return:dict type """ if username != None and password != None: if username == "polo" and password == "123456": return {"code":0,"msg":"登录成功!"} else: return {"code":1,"msg":"登录失败,用户名或密码错误!"} else: return {"code":1,"msg":"登录失败,用户名或密码为空!"} #设计登录测试用例类 class TestLogin(unittest.TestCase):#继承unittest.TestCase def test_login_success(self): '''登录成功''' username = "polo" password = "123456" expected_reponse = {"code":0,"msg":"登录成功....!"} #实际结果:调用login函数 actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用 #判断预期结果跟实际结果是否一样 断言 self.assertTrue(expected_reponse == actual_reponse) def test_login_error(self): '''登录失败''' username = '' password = '' expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"} actual_reponse = login(username, password) self.assertTrue(expected_reponse == actual_reponse)
结果:一个用例测试异常,不会影响下一个用例的执行。左边的Test Results 结果中可以查看异常的用例名称,右侧主要看该异常用例的错误类型
须知:
断言失败会抛出异常:AssertionError
一个测试用例,写一个断言。
多个用例之间互不影响:即上一个测试用例出现断言异常,不会影响下面的测试用例的执行(继续执行)
unittest框架中设置好了初始化,在测试用例这个类里面不需要再进行初始化__init__了。
(3).fixture----测试环境的搭建和销毁 (前置、后置条件)
setUp() 、tearDown()是unitttest框架的固定写法,不能改。
def setUp(self): '''前置条件''' pass def tearDown(self): '''后置条件''' pass
♥ ♥ 每执行一个测试用例,都会自动执行一遍setUp() 、tearDown(),举例:
#导入单元测试框架unittest import unittest """测试登录功能(函数)""" def login(username = None,password = None): """ :param username: 登录校验的用户名 :param password: 登录校验的密码 :return:dict type """ if username != None and password != None: if username == "polo" and password == "123456": return {"code":0,"msg":"登录成功!"} else: return {"code":1,"msg":"登录失败,用户名或密码错误!"} else: return {"code":1,"msg":"登录失败,用户名或密码为空!"} #设计登录测试用例类 class TestLogin(unittest.TestCase):#继承unittest.TestCase def setUp(self): '''前置条件''' print('连接数据库') def tearDown(self): '''后置条件''' print('断开数据库') def test_login_success(self): '''登录成功''' username = "polo" password = "123456" expected_reponse = {"code":0,"msg":"登录成功!"} #实际结果:调用login函数 actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用 #判断预期结果跟实际结果是否一样 断言 self.assertTrue(expected_reponse == actual_reponse) def test_login_error(self): '''登录失败''' username = '' password = '' expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"} actual_reponse = login(username, password) self.assertTrue(expected_reponse == actual_reponse)
结果如下:两个测试用例,前置、后置条件都会自动执行一遍
♥♥ 如果所有的测试用例,前置、后置条件都只执行一遍应该怎么办?------了解即可
使用setUpclass(cls),一个测试类中,只会执行一次的前置条件(类方法)
使用tearDownclass(cls),一个测试类中,只会执行一次的后置条件(类方法)
#导入单元测试框架unittest import unittest """测试登录功能(函数)""" def login(username = None,password = None): """ :param username: 登录校验的用户名 :param password: 登录校验的密码 :return:dict type """ if username != None and password != None: if username == "polo" and password == "123456": return {"code":0,"msg":"登录成功!"} else: return {"code":1,"msg":"登录失败,用户名或密码错误!"} else: return {"code":1,"msg":"登录失败,用户名或密码为空!"} #设计登录测试用例类 class TestLogin(unittest.TestCase):#继承unittest.TestCase @classmethod def setUpClass(cls): print('一个测试类中只会执行一次的前置条件') @classmethod def tearDownClass(cls): print('一个测试类中只会执行一次的后置条件') def setUp(self): '''前置条件''' print('连接数据库') def tearDown(self): '''后置条件''' print('断开数据库') def test_login_success(self): '''登录成功''' username = "polo" password = "123456" expected_reponse = {"code":0,"msg":"登录成功!"} #实际结果:调用login函数 actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用 #判断预期结果跟实际结果是否一样 断言 self.assertTrue(expected_reponse == actual_reponse) def test_login_error(self): '''登录失败''' username = '' password = '' expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"} actual_reponse = login(username, password) self.assertTrue(expected_reponse == actual_reponse)
'''
一个测试类中只会执行一次的前置条件
连接数据库
断开数据库
连接数据库
断开数据库
一个测试类中只会执行一次的后置条件
'''
注:运行代码是,右键-->run "unittest........"只有pycharm有这种功能
其他的编辑器,可以在运行的代码下面顶格加如下:
if __name__ == '__main__': #使用python去运行用例 unittest.main()
然后在terminal中用python运行:python lesson14 estcaseslogin.py
(4)Test Suite------测试集/测试套件,多个测试用例集合在一起
测试须知知识:
① unittest 中,测试用例的执行顺序
不是从上到下来执行的,是根据测试用例的名称----→ASCII编码,美国标准编码规范
测试用例名称的ASCII码的大小(前后顺序)来排列的
上面例子的两个测试用例名称,success error, e 在s的前面,error的用例先执行.
如果让success的用例先执行,error后执行,如下,加排序
def test_login_01_success(self):
pass
def test_login_02_error(self):
pass
②测试用例的执行方法
1)右键→run"Unittests in .................",运行当前模块(只有pycharm有unittest 运行,其他的编辑器就要用方法2)去运行)
2)增加 if __name__ == '__main__':
unittest.main()
在终端里面,使用python,命令行运行当前这个模块里的测试用例.
运行所有的测试用例怎么办???-----收集所有的用例 TestSuite&TestLoader()一起使用
① 测试用例的组织--会把测试用例的代码放到一个统一的文件夹中,目录当中tests / testcases
方法:在项目录下new 一个directory,命名为tests / testcases,把所有的测试用例代码放在这个文件夹里面
②项目根目录,new一个python file(比如:run_tests.py,运行所有的程序),最好跟tests / testcases平级
在这个模块里执行所有的程序,
包括:收集所有的测试用例、执行、生成测试报告
测试整个项目的时候,一般不会单独运行某个模块(调试的时候会运行单个模块)
③测试用例收集步骤
1)将所有待测的测试用例放在,项目目录下new 一个directory,命名为tests / testcases里
2)在run_test.py中,运行收集用例的代码
1)). import unittest
2)).初始化一个加载器 loader = unittest.TestLoader()
3)).加载器去收集所有的测试用例(testcases里的所有用例),放在testsuite里面 test_suite = loader.discover(case_path)
要获取testcases里的所有用例,就要获取该目录的绝对路径,该路径作为loader.discover()的参数(下面的例子testcases文件夹里有test_login.py文件,包含3个测试用例)
import unittest import os #初始化一个加载器 test_loadder loader = unittest.TestLoader() #获取测试用例目录的路径,测试用例都放在tesetcases文件夹里 dir_path = os.path.dirname(os.path.abspath(__file__)) case_path = os.path.join(dir_path,'testcases') #使用loader获取所有的测试用例 test_suite = loader.discover(case_path) #在case_path里面发现所有的测试用例 print(test_suite)#就是返回一个testSuite对象,列表存储的
测试用例收集完,怎么去执行测试用例呢?---------TextTestRunner()去执行测试集,用run方法
步骤:1).初始化一个执行器runner
2).runner.run(test_suite) -----传入测试集合执行import unittest
import os #初始化一个加载器 test_loadder loader = unittest.TestLoader() #获取测试用例目录的路径,测试用例都放在tesetcases文件夹里 dir_path = os.path.dirname(os.path.abspath(__file__)) case_path = os.path.join(dir_path,'testcases') #使用loader获取所有的测试用例 test_suite = loader.discover(case_path) #在case_path里面发现所有的测试用例 print(test_suite)#就是返回一个testSuite对象,列表存储的 #执行测试用例 #先初始化一个执行器runner runner = unittest.TextTestRunner() runner.run(test_suite)
'''
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_login.TestLogin testMethod=test_login_error>, <test_login.TestLogin testMethod=test_login_pwderror>, <test_login.TestLogin testMethod=test_login_success>]>]>, <unittest.suite.TestSuite tests=[]>]>
连接数据库
断开数据库
连接数据库
断开数据库
连接数据库
断开数据库
'''
上面的运行结果,没有测试报告。
如何去生成一个测试报告呢?-------with open('test_report.txt') as f:
import unittest import os #初始化一个加载器 test_loadder loader = unittest.TestLoader() #获取测试用例目录的路径,测试用例都放在tesetcases文件夹里 dir_path = os.path.dirname(os.path.abspath(__file__)) case_path = os.path.join(dir_path,'testcases') #使用loader获取所有的测试用例 test_suite = loader.discover(case_path) #在case_path里面发现所有的测试用例 print(test_suite)#就是返回一个testSuite对象,列表存储的 #执行 并生成text测试报告 with open('test_report.txt','a',encoding='utf-8') as f: runner = unittest.TextTestRunner(f) #f作为参数传入 runner.run(test_suite)
执行完,在run_test.py的同级目录下生成一个test_report.txt文件,记录结果信息
下图,将其中一个测试用例的数据修改,让其运行在断言的地方报错
报告结果中显示:..F (3个测试用例) 点 表示通过,F表示failure(测试用例的问题:①测试用例的预期结果不对 ②执行中确实存在bug),E 表示error,代码本身存在问题(添加1/0去运行,就会报错E)
但是在实际中,不会使用txt形式的报告,比较low,会换用HTML的报告.
HTML报告不是内置的,需要自己去下载HTML的测试报告的运行器和模板(HTMLTestRunnerNew.py),导入到pycharm的seite_package 或者直接导入项目目录下.
首先看一下HTMLTestRunner的源码
class HTMLTestRunner(Template_mixin): """ """ def __init__(self, stream=sys.stdout, verbosity=2,title=None,description=None,tester=None):
执行 生成HTML报告
#执行 并生成html测试报告 with open('test_report.html','wb') as f: runner = HTMLTestRunner( f, title='python29期第一次自动化测试报告', description='测试报告的描述', tester='polo' ) runner.run(test_suite)
结果:
总结:运行用例的流程
--------1.编写用例方法,用例方法放到一个目录中
--------2.写脚本run.py 收集用例 运行用例 loader收集用例 suite = discover()
--------3.得到测试集 test_suite
--------4.运行,test_runner = HTMLTestRunner
拓展知识(了解,用的比较少):不使用discover,只想运行某2个用例,不运行所有的用例
课程-数据分离结合excel实际应用(在25min左右时间段讲解)
from XXX import test_login,test_register
最后运行:runner.run(suit_total)
注:运行哪些用例,将用例add起来,是用list表示的,表示一个可迭代的对象。
(5) debug 设置断点 ------运行用debug run
①断点打在哪里?
♥知道会报错的那行,打断点,程序运行到这一行就会停止(该行不会运行)
♥如果不知道哪里会报错,就打在程序最开始的地方,再一步一步调试
②断点调试,pycharm中调式的类型
♥1)--------step over(F8) ,表示单步执行
♥2)-------------step into(F7),表示进入函数内
举例,在下面这行代码设置断点,运行后,执行step into ,就会进入login()函数中去
actual_response = login(username,password)
进入login()函数中以后,可以使用step into,或者step over.
3)-----------step into my code ,只能进入我自己写的代码,别人写的进不去,一般很少用,用step into比较多
4)-------------step out 退出函数
♥5)-------------run to Cursr 表示运行到光标的位置, 指定的行. 即打了断点以后,将光标放在想要运行指定的哪行代码上,点击run to Cursr ,就会运行指定的这行代码
6)------------evaluate expression 计算器
可以在进行调试的时候,对数据进行某些处理,查看结果
7)--------rerun 重新运行,点击会再次运行程序
8)--------resume program 运行到下一个断点(中间不会停顿)