什么是单元测试?
单元:函数或类
单元测试:测试函数或类
为什么要做单元测试?
好处:投入小,收益大,能够精准的更早的发现bug
单元测试跟我们有什么关系?
Python很难测试Java的单元
单元测试一般是开发或者测试开发做的
自动化测试:
集成,系统,验收
单元测试框架:
可以用来进行:单元测试,集成测试,系统测试
内置的单元测试框架:unittest
#示例1:
def login(username=None,password=None):
if username!=None and password!=None:
if username=='yuz' and password=='123456':
return {'msg':'login success'}
else:
return {'msg':'username or password error'}
else:
return {'msg':'username or password is empty'}
if __name__=='__main__':
print(login()) #{'msg': 'username or password is empty'}
print(login('yuz','')) #{'msg': 'username or password error'}
#测试用例1
import unittest
class TestLogin(unittest.TestCase):
#测试用例的方法
def test_login_success(self):
username='yuz'
password='123456'
expected_response={'msg':'login success'}
#调用被测试的单元
actual_response=login(username,password)
#判断预期结果和实际是否存在某种关系,断言assert
# self.assertEqual(expected_response,actual_response)
self.assertTrue(expected_response==actual_response)
unittest的注意事项:
unittest:TestCase、TestSuite、TextTestRunnner、fixture
1、模块名:test_.py
2、类名:Test
3、测试用例的方法名称:test_*
4、TestLogin(unittest.TestCase) #不要丢掉里面的类,类的继承
5、测试用例的方法中,test_*(self)
6、AssertionError:异常,用例失败,只影响该条用例,其他用例不受影响,保证了用例的独立性
7、类没有调用(类没有声明对象),就可以直接执行
8、一个用例一个断言
self.assertEqual(expected_response,actual_response)
self.assertTrue(expected_response==actual_response)
#这两者是等价的
self.assertGreater(4>3)
self.assertTrue(4,3)
#这两者也是等价的
#测试用例2
class TestLogin(unittest.TestCase):
#测试用例1的方法
def test_login_success(self):
username='yuz'
password='123456'
expected_response={'msg':'login success'}
#调用被测试的单元
actual_response=login(username,password)
#判断预期结果和实际是否存在某种关系,断言assert
# self.assertEqual(expected_response,actual_response)
self.assertTrue(expected_response==actual_response)
#测试用例2的方法
def test_login_error(self):
username=''
password=''
expected_response={'msg':'username or password error'}
#调用被测试的单元
actual_response=login(username,password)
#判断预期结果和实际是否存在某种关系,断言assert
self.assertTrue(expected_response==actual_response)
fixture:
前置条件:setUp()
def setUp(self):
pass
后置条件:tearDown()
def tearDown(self):
pass
在用例执行之前执行setUp(),在用例执行之后执行tearDown()
有多少条用例,就执行多少遍setUp()和tearDown()
如果想在一个测试类当中,只执行一次的话前置和后置,需要用到类方法:setUpClass(cls)和tearDownClass(cls)
#示例1:
class TestLogin(unittest.TestCase):
#用多少个用例就执行多少次setUp(self)和tearDown(self)
def setUp(self):
print('连接数据库')
def tearDown(self):
print('断开数据库')
#一个测试类中只执行一次前置和后置
@classmethod
def setUpClass(cls):
print('一个测试类只执行一次的前置')
@classmethod
def tearDownClass(cls):
print('一个测试类只执行一次的后置')
#测试用例1的方法
def test_login_success(self):
username='yuz'
password='123456'
expected_response={'msg':'login success'}
#调用被测试的单元
actual_response=login(username,password)
#判断预期结果和实际是否存在某种关系,断言assert
# self.assertEqual(expected_response,actual_response)
self.assertTrue(expected_response==actual_response)
TestSuite:
测试集/测试套件,测试用例的集合,
通常使用列表存储测试用例
unittest测试用例的执行顺序:
不是从上到下
根据测试用例的名称:ASCII码
如果想自己调整测试用例的顺序,就可以在测试用例的方法名称上加数字,比如:test_login_01_success(self)
断点:
调试的时候使用debug模式,打断点
在可能出错的地方打断点,如果不知道哪行会出错,就打在最开始的位置,一行一行调试
打断点如果运行?
step over:单步执行,运行到下一行
step into:执行到函数的内部
step into my code:进入自己写的代码,别人写的进不去
step out:退出函数
run to cursor:运行到指定的行
evaluate:查找值并可对查找到的值进行其他运算
rerun:重新运行
resume program:运行到下一个断点
stop:停止
view breakpoints:显示所有的断点
mute breakpoints:关闭所有的断点
用例的组织:
我们会把测试用例的代码放到一个统一的文件夹当中,目录当中:tests/testcases
测试用例运行的方式:
右击unittest运行【只能运行当前的模块】
终端运行:【一般终端运行,建议加上下面的代码,不加也是OK的,最好是加上】【只能运行当前的模块】
if __name__=='__main__':
#使用Python运行这个模块里面的测试用例
unittest.main()
运行所有的测试用例:run_test.py
收集用例:
需要把每个测试用例模块当中的测试用例收集到一起,一起运行
那就需要我们在根目录下建一个run_test.py文件运行程序:收集所有的测试用例,并执行,生成测试报告
run_test.py:
import unittest
import os
#初始化一个加载器,test_loader
loader=unittest.TestLoader()
#获取测试用例目录的路径
dir_path=os.path.dirname(os.path.abspath(__file__))
cases_path=os.path.join(dir_path,'tests')
#使用loader收集所有的测试用例
test_suit=loader.discover(cases_path) #discover得到的就是一个TestSuite,
#所以不需要再声明一个TestSuite了,直接用变量接收就行
#以列表的形式收集所有的测试用例
#执行测试用例
runner=unittest.TextTestRunner()
runner.run(test_suit)
#生成测试报告
with open('test_reports.txt','w',encoding='utf-8') as f:
runner=unittest.TextTestRunner(f)
runner.run(test_suit)
#注:生成的测试报告中,.表示测试用例通过,F表示测试用例执行失败【要不是预期结果不对,要不就是bug】,E表示代码写的有问题
#HTML样式的测试报告,不是内置的,是需要自己准备的:HTMLTestRunnerNew.py
with open('test_reports.html','wb') as f:
runner=HTMLTestRunner(
f,
title='Python第29期测试报告',
description='测试报告',
tester='lemon'
)
runner.run(test_suit)
运行用例的流程:
1、写用例方法,把用例方法放到目录当中
2、写脚本run_test.py,收集用例,运行用例
3、loader收集用例,suite=discover()
4、得到测试集suite
5、运行,test_runner=HTMLTestRunner()
【
#不想运行所有的用例,只想运行其中的某些模块
suite_login=loader.loadTestsFromModule(test_login)
suite_register=loader.loadTestsFromModule(test_register)
#初始化一个suite
suite_total=unittest.TestSuite()
suite_total.addTests([suite_login,suite_register]) #合并测试集
#HTML样式的测试报告,不是内置的,是需要自己准备的:HTMLTestRunnerNew.py
with open('test_reports.html','wb') as f:
runner=HTMLTestRunner(
f,
title='Python第29期测试报告',
description='测试报告',
tester='lemon'
)
runner.run(suite_total)
】
Excel操作:
通过Python操作Excel,是因为现在主流的测试用例的数据是写在Excel里面的
Excel是一种比较规范的数据格式
xmind不是一种规范的数据格式
自动化测试的用例数据一般放在比较规范的数据格式中:Excel,CSV,yaml
openyxl:
专门处理Excel表格的,只支持xlsx格式(2003版以后)
安装:pip install openpyxl
或者是使用国内源安装:pip install -i 国内地址
读取Excel文件
1、打开文件,得到一个工作簿
2、选择表单
3、通过行列循环读取数据
import openpyxl
from openpyxl.worksheet.worksheet import Worksheet
#第一步:打开文件
workbook=openpyxl.load_workbook('/Users/qinningmeng/PycharmProjects/test_auto/lemonban/day1_0414/data/cases.xlsx')
print(workbook)
#第二步:获取表单
sheet=workbook['sheet1']
#sheet: Worksheet=workbook['sheet1'] #小技巧:加上:Worksheet,表示sheet是什么样的数据类型,使用sheet.时就会出现提示
print(sheet)
#第三步:获取某一行,某一列的单元格
#cell方法得到的是一个单元格对象,不是case_id值
cell=sheet.cell(row=2,column=3)
print(cell)
#获取单元格的数据
print(cell.value)
#获取所有的行,每一行的数据用元组存储
rows=list(sheet.rows)
print(rows)
for row in rows:
#row代表的是一行数据
for cell in row:
print(cell.value)
写入操作,直接对cell当中的value属性赋值
cell=sheet.cell(row=2,column=3)
cell.value
======>等价于
sheet.cell(row=2,column=3).value
cell.value='login failed' #直接赋值为新值就是修改,但是修改完之后文件中的该项并未做修改,因为未保存文件
#保存修改,工作簿save()
workbook.save('文件路径')
#关闭文件
workbook.close()
注意:修改文件之后记得保存并关闭,平时很少会用到修改文件操作
Excel读取到的数据,如何放到测试用例里面呢?这里就用到了封装,使用类封装[也可以使用函数,只不过类和对象比较方便]
import openpyxl
class ExcelHandler:
def __init__(self,file_path):
self.file=file_path
self.workbook=None #这里不初始化workbook也是可以的,只是如果不初始化,当下面的方法定义了对象属性时,就不清楚这个对象到底有多少个属性,比较混乱,所以把这个对象所有的属性都在这里先初始化一下就很清楚的知道这个对象有多少个属性
def open_file(self):
# print('打开文件:{}'.format(self.file))
workbook=openpyxl.load_workbook(self.file)
self.workbook=workbook
return workbook
def get_sheet(self,sheetname):
workbook=self.open_file()
# print('获取表格:{}'.format(sheetname))
return workbook[sheetname]
def read_sheet(self,name):
#读取数据,每一行数据存放到列表或者是字典当中
sheet=self.get_sheet(name)
# print('读取内容')
rows=list(sheet.rows)
data=[]
#获取标题
header=[]
for row in rows[0]:
header.append(row.value)
#添加数据
for row in rows[1:]:
dict={}
for index,cell in enumerate(row):
dict[header[index]]=cell.value
data.append(dict)
return data
def write(self,name,row,column,data):
#写入单元格数据
sheet=self.get_sheet(name)
sheet.cell(row,column).value=data
self.save()
self.close()
def save(self):
self.workbook.save(self.file)
def close(self):
# print('关闭文件:{}'.format(self.file))
self.workbook.close() #因为打开的是一个工作簿,所以关闭的也应该是工作簿,而不能是:self.file.close()
#示例1:
if __name__=='__main__':
excel=ExcelHandler('cases.xlsx')
print(excel.get_sheet('sheet1'))
print(excel.read_sheet('sheet1'))
将封装的测试文件如何用到我们的模块当中?
之前是把测试数据写到了测试方法当中,方法当中除了测试数据不同,其他都是一样的,
要把测试数据提取出来,写一个方法就行了
cases=[
{'username':'23','password':'1234','expected':{'msg':'username or password error'}},
{'username':'yuz','password':'123456','expected':{'msg':'login success'}}
]
class TestLogin(unittest.TestCase):
def test_login(self):
# username='yuz'
# password='123456'
# expected_response={'msg':'login sccess'}
for case in cases:
username=case['username']
password=case['password']
expected_response=case['expected']
#调用被测试的单元
actual_response=login(username,password)
#判断预期结果和实际是否存在某种关系,断言assert
self.assertTrue(expected_response==actual_response)
for循环是有问题的:
1、因为如果其中有一条用例不通过,下面的用例都不会执行,【当前面的数据报错,后天的用例就不会执行了】
2、for循环不管是多少条用例,都是显示一条用例,因为只有一个用例的方法【每个用例数据不是独立的,被当成一个用例在执行】
由此引入了数据驱动测试:ddt---data driven testing
1、有多少条数据,就有多少条用例
2、每条数据之间是独立的,前面报错不影响后面的执行
ddt 数据驱动测试
1、安装:pip install ddt
2、导入:import ddt
3、测试类加上:@ddt.ddt【在类方法上面加:@ddt.ddt】
@ddt.ddt
class TestLogin(unittest.TestCase):
4、需要进行数据驱动的方法上加:@ddt.data(*测试数据) 【数据要放到哪个方法上去,要告诉它】
@ddt.data(*cases) # * 表示解包
def test_login(self):
#之前星号的用法:不定长参数
def add(*args):
print(args)
add(3,4,5,6)
#解包:函数调用的时候,实际参数前加星号,表示解包
actual_param=[3,4,5,6] #或者 actual_param=(3,4,5,6)
add(*actual_param) #这里就是把actual_param列表的[]去掉,得到的结果是:(3,4,5,6)
5、测试用例方法用参数接收 def test_login(self,case_info)【方法中加一个变量,随便什么名字,用来接收每条数据】
@ddt.data(*cases)
def test_login(self,case_info):
#case_info 相当于cases中的每一条数据 ----》{'username':'23','password':'1234','expected':{'msg':'username or password error'}
如何将数据来源改为读取Excel?
from lemonban.day1_0414.ExcelHandler import ExcelHandler
cases=ExcelHandler(‘data/cases.xlsx’).read_sheet(‘sheet1’)