测试理论回顾:
黑盒测试:是针对整个产品系统进行的测试,验证系统是否满足需求规格的定义,及软件产品的正确性和性能等是否满足其需求规格的要求。
灰盒测试:是介于白盒测试与黑盒测试之间的一种测试,灰盒测试多用于集成测试阶段,不仅关注输出、输入的正确性,同时也关注程序内部的情况。
白盒测试:是通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码内部在算法,路径,条件等等中的缺点或者错误,进而加以修正。
单元测试:
单元测试是针对程序的最小单元来进行正确性检验的过程。单元:一个单元可能是单个程序、类、对象、方法(函数)等。 优点是可以减少BUG;快速定位BUG;提高代码质量;减少调试时间。缺点是周期时间长;耗费资源(主要是人力资源);能力要求高。
一、单元测试流程
1. 单元测试-计划:1) 确定要测试代码范围;2) 评估标准(确定被测代码的覆盖率)
2. 测试策略-设计:1) 拿到开发代码进行调整(保证单元间可独立执行)
3. 测试策略-实现:1) 根据调整好的代码-画流程图;2) 根据流程图画流图-确定复杂度、路径;3) 根据复杂度和路径确定测试用例(测试数据)
4. 单元测试-执行:1) 使用测试框架(UnitTest)编写单元测试用例;2) 测试用例(代码)和测试数据分离;3) 生成测试报告
二、单元测试-计划
确定要测试代码以及确定这些被测代码的评估标准、优先级等说明:1. 确定单元测试范围(哪些代码要做单元测试);评估标准-(被测代码的逻辑覆盖率)
1.1、如何确定20%代码
确定单元测试的代码测试范围,我们依据:二八原则(20%的代码中隐藏了80%的缺陷)
1. 频率:使用频率高的代码段;
2. 复用性:(是否已被复用,是否被别的地方引用,如果被别的地方调用这个函数,没问题,基本不用测)
1). 全新(指没有被复用的代码)
2). 部分复用
3). 全部复用
3. 开发人员:
1). 技术(指由技术水平不那么高的开发人员写的代码)
2). 业务(指由对业务不那么熟悉的开发人员写的代码)
4. 复杂度:业务逻辑复杂度(一般认为圈复杂度在7级以上代码包括在20%的代码中)
最后,我们会得到一张表,来列名我们要测的代码范围:
1.2、评估标准(覆盖率)如何确定逻辑覆盖率
例如,我们拿到一段代码,实现的一个需求,我们首先要画流程图(使用统一规定标准图形,描述程序运行的具体步骤),然后以此来确定覆盖率,并且以后可以根据流程图画流图
1. 语句覆盖率,说明:非分支非判断的代码,覆盖率计算方式:被覆盖语句/总语句,例如,流程图里有3个语句,对于一个测试数据,它能覆盖2个语句,则语句覆盖率为2/3
2. 分支覆盖率,说明:判断语句的分支,例如if判断有两个分支,覆盖率计算方式:覆盖分支/总分支数,例如流程图里的if判断有2个分支,对于一个测试数据,它能覆盖1个分支,则分支覆盖率为1/2
3. 条件覆盖率,说明:一个条件有两个结果 true、false,所以一个条件分母为2,两个条件分母为4...例如:if username=='admin' and pwd=='123456':这里就有两个条件,分母为4
注意:条件之间使用逻辑符 and连接,第一个条件如果失败,不会在去判断第二个条件,如果测试数据是username==ad, pwd就不会判断,覆盖率为1/4;如果为or第一个条件失败回去判断第二条件
4. 路径覆盖率,说明:路径就是从开始-到结束的过程, 覆盖率计算方式:覆盖路径/全部路径
注意:路径的分子永远为1, 有时候看流程图路径有4条,但有一条永远不可成立,所以实际路径为3条,计算路径覆盖率时分母就为3
5. 分支条件覆盖率,说明:分支和条件的组合,1.分子=分支覆盖率的分子+条件覆盖率的分子;2.分母=分支覆盖率的分母+条件覆盖率的分母
三、单元测试策略-设计
单元测试策略:针对单元测试选择测试的一种方式;
单元测试策略方式:
1. 自上到下,方式:从最上层函数往下开始逐层测试,缺点(成本高)
2. 自下到上,方式:从最底层函数往上开始逐层测试,缺点(周期长,需要开发写完所有代码才能开始测试)
3. 孤立策略【推荐使用】方式:选择需要进行测试的函数进行测试,优点:选择重要的代码进行测试,测试构成中免不了测的某些函数会调用别的函数,所以一定要学会打桩
注意:打桩-->打桩就是模拟编写一个我们需要引用的函数,模拟定义被调用的函数名,提示:一般我们只模拟写个函数名,直接返回相应的结果即可(return 结果;pass)
示例:def fun_1(self):
return true
四、单元测试策略-实现
测试策略实现:把我们选定的代码,而且保证选定的代码能独立运行(已经打完桩),转向流程图、流图及用例的过程
测试策略实现如何操作:1. 将测试代码转换成流程图;2. 根据流程图转换为流图,有了流图测试用例就出来了
什么是流图?
概念:表达程序业务逻辑的复杂度一种示意图
构成:
1) 圈:判断条件(一个条件一个圈)、语句块(一条或多条语句,挨着的语句可以用一个圈表示)两者都圈
2) 线:箭头指向的线,用来连接圈和圈的指向
流图的目的:
1) 确定单元的复杂度级别
2) 确定测试用例
备注: 路径的个数为复杂度的级别(一条路径为1个复杂度级别),有N个条件最少有N条路径,最多N+1条路径
* 路径:2 (1-2-3,1-3);复杂度:2;用例个数:2
五、单元测试——执行
通过单元测试框架对要进行测试代码的实践过程
需求:通过Python语言编写一个运算的类(Calc),类中包含两个函数:add(self,a,b) 返回a+b之和;sub(self,a,c) 返回a-c之差
- 导包 UnitTest 、Calc类
- 新建单元测试类 Test01(集成 unitTest.TestCase)
- 新建testAdd()函数
- 导入Calc类中的add函数
- 添加断言
- 新建testSub()函数
- 导入Calc类中的sub函数
- 添加断言
- 执行测试:unittest.main()
import unittest
# 导入要测试的 Calc类
from UT.Day02.Code.test01_lx01import Calc
class Test_Calc(unittest.TestCase):
def setUp(self):
print("setUp被执行")
def tearDown(self):
print("tearDown被执行")
def test_add(self):
result=Calc().add(10,10)
print("test_add方法被执行")
self.assertEqual(result,20)
def test_sub(self):
result=Calc().sub(10,20)
print("test_sub方法被执行")
self.assertEqual(result,-10)if __name__== '__main__':
# main运行当前类中所有test开头的方法
unittest.main()
数据直接写入代码中,如果数量庞大,我们需要频繁改动数据或复制冗余代码进行实现数据测试目的。做不到数据分离(代码和数据分开),不便维护
参数化
根据需求动态获取数据并进行赋值的过程
参数化方式:XML格式(1); CSV格式(2);JSON串 (3);TXT文本(4)
提示:括号内为推荐使用优先级,从小到大;1-2为扩展了解,3-4建议重点了解
XML格式:
XML是一种标记语句,很类似HTML标记语言;后缀 .xml;XML是传输和存储数据,焦点在数据;HTML是显示数据,焦点在外观;XML不适合大量参数化数据时使用
XML格式:
<?xml version="1.0" encoding="UTF-8"?>
<book category="面授">
<title>单元测试</title>
<author>XST</author>
<year>2008</year>
<price>39.95</price>
</book>1. 必须有XML声明语句:<?xml version="1.0" encoding="UTF-8"?>
2. 必须要有一个根元素,如:<book>
3. 标签大小写敏感
4. 属性值用双引号
5. 标签成对
6. 元素正确嵌套
7. 标签名可随意命名,但有以下限制(注意:命名允许重复)
1) 不能以数字或者标点符号开始参
2)不能以字符 “xml”(或者 XML、Xml)开始
3) 名称不能包含空格
需求:对三角形案例单元测试使用XML格式进行参数化
被测代码段:
class Sjx(): def sjx(self,a,b,c):
# 判断是否为三角形
if a+b>c and a+c>b and b+c>a:
# 判断是否为等边三角形
if a==b and b==c:
return "等边三角形"
elif a==b or a==c or b==c:
return "等腰三角形"
else:
return "普通三角形"
else:
return "不是三角形"
if __name__ == '__main__':
print(Sjx().sjx(2,3,4))--------------------------------------------------------------------------------------------------------------------------
操作步骤:1. 编写XML数据文件;2. 编写读取XML模块函数;3. 单元测试模块中引用XML读取函数;4. 执行
编写XML数据文件:
<?xml version="1.0" encoding="UTF-8" ?>
<SJX>
<bian>
<b1>2</b1>
<b2>3</b2>
<b3>4</b3>
<expect>普通三角形</expect>
</bian>
<bian>
<b1>2</b1>
<b2>2</b2>
<b3>2</b3>
<expect>等边三角形</expect>
</bian>
<bian>
<b1>2</b1>
<b2>2</b2>
<b3>3</b3>
<expect>等腰三角形</expect>
</bian>
<bian>
<b1>2</b1>
<b2>3</b2>
<b3>2</b3>
<expect>等腰三角形</expect>
</bian>
<bian>
<b1>3</b1>
<b2>2</b2>
<b3>2</b3>
<expect>等腰三角形</expect>
</bian>
<bian>
<b1>3</b1>
<b2>2</b2>
<b3>1</b3>
<expect>不是三角形</expect>
</bian>
<bian>
<b1>1</b1>
<b2>2</b2>
<b3>3</b3>
<expect>不是三角形</expect>
</bian>
<bian>
<b1>2</b1>
<b2>3</b2>
<b3>1</b3>
<expect>不是三角形</expect>
</bian>
</SJX>编写读取XML模块函数
# 导包
minidomfrom xml.dom import minidom
class Read_Xml():
def readXml(self,node,num,nodeChild):
# 解析xml文档
dom=minidom.parse("../DataPool/sjx.xml")
# 获取文档对象
root=dom.documentElement
# 获取bian元素
element=root.getElementsByTagName(node)[int(num)]
# 获取指定bian元素 如b1
return element.getElementsByTagName(nodeChild)[0].firstChild.data
def get_len(self,node):
# 解析xml文档
dom=minidom.parse("../DataPool/sjx.xml")
# 获取文档对象
root=dom.documentElement
# 获取bian元素
return len(root.getElementsByTagName(node))
if __name__ == '__main__':
print(Read_Xml().readXml("bian",0,"b3"))
print(Read_Xml().get_len("bian"))单元测试模块中引用XML读取函数;执行
# 导包 unittest、三角形函数、读取xml数据类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_xml import Read_Xml
# 实例化三角形
sjxClass=Sjx()
# 实例化读取xml类
readXmlClass=Read_Xml()
class Test_Xml(unittest.TestCase):
def test001(self):
for i in range(readXmlClass.get_len("bian")):
# 目的测试三角形程序
result=sjxClass.sjx(int(readXmlClass.readXml("bian",i,"b1")), int(readXmlClass.readXml("bian", i, "b2")), int(readXmlClass.readXml("bian", i, "b3")))
# 添加断言,判断三角形程序返回的结果是否符合我们预期结果 self.assertEqual(result,readXmlClass.readXml("bian", i, "expect")) print(readXmlClass.readXml("bian",i,"b1"), readXmlClass.readXml("bian", i, "b2"), readXmlClass.readXml("bian", i, "b3"), readXmlClass.readXml("bian", i, "expect"),"--》验证通过")重点分析:
1. 导入XML包 from xml.dom import minidom
2. 加载解析 dom=minidom.parse(filename)
3. 获取对象 root=dom.documentElement
4. 获取子元素 aas=root.getElementsByTagName(one)[0]
5. 获取子元素值 aas.getElementsByTagName(two)[0].firstChild.data
CSV格式:
CSV是一种以逗号做分割的表格格式; 后缀 .csv
使用CSV实现三角形案例参数化-操作步骤
1. 创建CSV文件
2. 编写CSV读取模块函数
3. 单元测试-引用CSV读取函数
4. 执行创建CSV文件:
2,2,2,等边三角形2,2,3,等腰三角形2,3,2,等腰三角形3,2,2,等腰三角形1,2,3,不是三角形2,3,1,不是三角形3,2,1,不是三角形2,3,4,普通三角形编写CSV读取模块函数:
# 导包
import csv
class Read_Csv():
def readCsv(self):
# 打开csv
with open("../DataPool/sjx.csv","r",encoding='utf-8') as f:
datas=csv.reader(f)
# 新建空列表,把单行返回的列表添加成整体的列表
lines=[]
for data in datas:
# 添加数组
lines.append(data)
return lines
if __name__ == '__main__':
print(Read_Csv().readCsv())单元测试-引用CSV读取函数;执行:
# 导入unittest、三角形函数、csv读取参数类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_csv import Read_Csv
# 实例化三角形
sjxClass=Sjx()
# 实例化读取csv工具
readCsvClass=Read_Csv()
class Test_Csv(unittest.TestCase):
# 测试三角形函数程序
def test001(self):
for i in range(len(readCsvClass.readCsv())): result=sjxClass.sjx(int(readCsvClass.readCsv()[i][0]),
int(readCsvClass.readCsv()[i][1]),
int(readCsvClass.readCsv()[i][2]))
# 断言:三角新运行完后返回的结果和预期结果做对比 self.assertEqual(result,readCsvClass.readCsv()[i][3])
print(readCsvClass.readCsv()[i][0], readCsvClass.readCsv()[i][1], readCsvClass.readCsv()[i][2], readCsvClass.readCsv()[i][3],"---》验证通过")
if __name__ == '__main__':
unittest.main()重点分析:
1. 导包 import csv
2. 打开csv文件:
with open("../Data/sjx.csv","r",encoding="utf-8") as f:
lines=csv.reader(f)
JSON串:
一种轻量级数据交换格式;后缀名 .json ;提示: 接口测试一般使用JSON为接口传递数据规范格式
格式:{"name":"张三","age":28};提示:由键值对组成,健名和值之间使用分号(:)分割,多个键值对之间使用逗号(,)分割
使用JSON实现三角形案例参数化-操作步骤
1. 编写JSON文件
2. 编写JSON读取模块函数
3. 单元测试-引用JSON读取函数
4. 执行编写JSON文件 :
{"data": [ {"b1":2,"b2":2,"b3":2,"expect":"等边三角形"}, {"b1":2,"b2":2,"b3":3,"expect":"等腰三角形"}, {"b1":2,"b2":3,"b3":2,"expect":"等腰三角形"} ]}编写JSON读取模块函数 :
# 导包 json
import json
# 打开文件流
class Read_Json():
def readJson(self):
with open("../DataPool/sjx.json","r",encoding="utf-8") as f:
# 调用load()
datas=json.load(f)
# 返回字典data键名对应的值
return datas["data"]
if __name__ == '__main__':
print(Read_Json().readJson())单元测试-引用JSON读取函数;执行 :
# 导包 unittest 、三角形函数程序、读取json类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_json import Read_Json
# 实例化三角形
sjxClass=Sjx()
# 实例化读取JSON类
readJsonClass=Read_Json()
class Test_Json(unittest.TestCase):
# 测试三角形
def test001(self):
for i in range(len(readJsonClass.readJson())):
# 调用三角形函数
result=sjxClass.sjx(int(readJsonClass.readJson()[i]["b1"]), int(readJsonClass.readJson()[i]["b2"]), int(readJsonClass.readJson()[i]["b3"]))
# 断言 三角形返回的结果是否与预期结果相符 self.assertEqual(result,readJsonClass.readJson()[i]["expect"])
# 打印运行参数及结果
print(readJsonClass.readJson()[i]["b1"], readJsonClass.readJson()[i]["b2"], readJsonClass.readJson()[i]["b3"], readJsonClass.readJson()[i]["expect"],"---> 通过!")
if __name__ == '__main__':
unittest.main()难点分析
1. 导入JSON包(import JSON)
2. 打开JSON文件并解析
with open('../DataXML/sjx.json','r',encoding='utf-8') as f:
file=json.load(f)
TXT文本:
一种纯文本格式; 后缀名 .txt;TXT文本优点: 编写测试数据方便;使用模块函数读取时便捷
使用TXT实现三角形案例参数化-操作步骤
1. 创建txt文本并写入测试数据
2. 编写读取txt模块函数
3. 单元测试-引用JSON读取函数
4. 执行创建txt文本并写入测试数据:
第一条边,第二条边,第三条边,预期结果
2,2,2,等边三角形
2,2,3,等腰三角形
2,3,2,等腰三角形
3,2,2,等腰三角形
1,2,3,不是三角形
2,3,1,不是三角形
3,2,1,不是三角形
2,3,4,普通三角形编写读取txt模块函数 :
class Read_Txt():
def readTxt(self):
# 打开txt文件流
with open("../DataPool/sjx.txt","r",encoding="utf-8") as f:
# 通过文件流调用读取的方法读取数据--->所有行
datas=f.readlines()
# 新建列表 --》添加分割后的单行列表数据
lines=[]
# 遍历
for data in datas:
'''
strip():去除字符串前后回车,空格,tab键
split():使用指定符号进行分割字符串并以列表格式返回分割后的数据
'''
# 把分割后的单行列表数据添加到整体的列中,并返回
lines.append(data.strip().split(","))
# 返回数据 [1:] 返回从下标1开始返回数据
return lines[1:]
'''
f.read() #读取所有数据
f.readline() # 读取单行
f.readlines() # 读取所有行
'''
if __name__ == '__main__':
print(Read_Txt().readTxt())单元测试-引用JSON读取函数;执行 :
# 导包 unittest、三角形、txt读取
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_txt import Read_Txt
# 实例化三角形
sjxClass=Sjx()
# 实例化txt
readTxtClass=Read_Txt()
class Test_Txt(unittest.TestCase):
# 测试三角形程序
def test001(self):
for i in range(len(readTxtClass.readTxt())):
# 调用三角形方法
result=sjxClass.sjx(int(readTxtClass.readTxt()[i][0]),
int(readTxtClass.readTxt()[i][1]),
int(readTxtClass.readTxt()[i][2]))
# 调用断言,判断三角形程序执行后的结果是否与预期结果相符
self.assertEqual(result,readTxtClass.readTxt()[i][3])
# 为了查看方便,我把执行成功后的数据打印一下
print(readTxtClass.readTxt()[i][0], readTxtClass.readTxt()[i][1], readTxtClass.readTxt()[i][2], readTxtClass.readTxt()[i][3],"-->通过!")
if __name__ == '__main__':
unittest.main()难点分析:导包:不需要 读取方法:readlines()
1. 如何读取txt文本?
with open(r'../DataXML/三角形.txt','r',encoding='utf-8') as f:
2. 如何去除行尾/n换行符?
line.strip()
--------------------------------------------------------------------------------
HTML报告生成
如何生成HTML报告?导入HTML报告模板模板:HTMLTestRunner.py
编写生成HTML模块
# 导入unittest包
import unittest
# 导入 HTMLTestRunner模板包
from UnitTest.Day02.ReadData.HTMLTestRunner import HTMLTestRunner
#导入时间包
import time
# 定义测试模块路径
dirpath='.'
discorver=unittest.defaultTestLoader.discover(dirpath,pattern='test*.py')
if __name__=='__main__':
#存放报告的文件夹
report_dir='../TestReport'
#报告名称含时间,时间格式
now=time.strftime("%Y-%m-%d %H_%M_%S")
#报告完整路径+名称
report_name=report_dir+'/'+now+'result.html'
#打开报告写入结果
with open(report_name,'wb')as f:
runner=HTMLTestRunner(stream=f,title="UnitTest Report-SJX",description='Test Case Report')
runner.run(disconver)