一、实验目的
1)掌握单元测试的方法
2) 学习XUnit测试原理及框架;
3)掌握使用测试框架进行单元测试的方法和过程。
二、实验内容与要求
1、了解单元测试的原理与框架
1.1 单元测试原理
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
单元测试的内容包括:模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试。
(1)模块接口测试
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。测试接口正确与否应该考虑下列因素:
-输入的实际参数与形式参数的个数是否相同
-输入的实际参数与形式参数的属性是否匹配
-输入的实际参数与形式参数的量纲是否一致
-调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
-调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
-调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
-调用预定义函数时所用参数的个数、属性和次序是否正确;
-是否存在与当前入口点无关的参数引用;
-是否修改了只读型参数;
-对全程变量的定义各模块是否一致;
-是否把某些约束作为参数传递。
如果模块功能包括外部输入输出,还应该考虑下列因素:
-文件属性是否正确;
-OPEN/CLOSE语句是否正确;
-格式说明与输入输出语句是否匹配;
-缓冲区大小与记录长度是否匹配;
-文件使用前是否已经打开;
-是否处理了文件尾;
-是否处理了输入/输出错误;
-输出信息中是否有文字性错误。
-局部数据结构测试;
-边界条件测试;
-模块中所有独立执行通路测试;
(2)局部数据结构测试
检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:
-不合适或不相容的类型说明;
-变量无初值;
-变量初始化或省缺值有错;
-不正确的变量名(拼错或不正确地截断);
-出现上溢、下溢和地址异常。
(3)边界条件测试
边界条件测试是单元测试中最重要的一项任务。众所周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。边界条件测试是一项基础测试,也是后面系统测试中的功能测试的重点,边界测试执行的较好,可以大大提高程序健壮性。
(4)独立路径测试
在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。测试目的主要是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。具体做法就是程序员逐条调试语句。常见的错误包括:
-误解或用错了算符优先级;
-混合类型运算;
-变量初值错;
-精度不够;
-表达式符号错。
(5)错误处理测试
检查模块的错误处理功能是否包含有错误或缺陷。例如,是否拒绝不合理的输入;出错的描述是否难以理解、是否对错误定位有误、是否出错原因报告有误、是否对错误条件的处理不正确;在对错误处理之前错误条件是否已经引起系统的干预等。
通常单元测试在编码阶段进行。在源程序代码编制完成,经过评审和验证,确认没有语法错误之后,就开始进行单元测试的测试用例设计。利用设计文档,设计可以验证程序功能、找出程序错误的多个测试用例。对于每一组输入,应有预期的正确结果。
1.2 测试框架
xUnit是各种代码驱动测试框架的统称,这些框架可以测试 软件的不同内容(单元),比如函数和类。xUnit框架的主要优点是,它提供了一个自动化测试的解决方案。可以避免多次编写重复的测试代码。
底层是xUnit的framwork,xUnit的类库,提供了对外的功能方法、工具类、api等 TestCase(具体的测试用例)去使用framwork TestCase执行后会有TestResult 使用TestSuite控制TestCase的组合 TestRunner执行器,负责执行case TestListener过程监听,监听case成功失败以及数据结果,输出到结果报告中 |
Unit测试框架包括四个要素:
(1)测试目标(对象)
一组认定被测对象或被测程序单元测试成功的预定条件或预期结果的设定。Fixture就是被测试的目标,可以是一个函数、一组对象或一个对象。 测试人员在测试前应了解被测试的对象的功能或行为。
(2)测试集
测试集是一组测试用例,这些测试用例要求有相同的测试Fixture,以保证这些测试不会出现管理上的混乱。
(3)测试执行
单个单元测试的执行可以按下面的方式进行:
第一步 编写 setUp() 函数,目的是:建立针对被测试单元的独立测试环境;举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。
第二步 编写所有测试用例的测试体或者测试程序;
第三步 编写tearDown()函数,目的是:无论测试成功还是失败,都将环境进行清理,以免影响后续的测试;
(4)断言
断言实际上就是验证被测程序在测试中的行为或状态的一个函数或者宏。断言的失败会引发异常,终止测试的执行。
1.3 面向特定语言的,基于xUnit框架的自动化测试框架
Junit : 主要测试用Java语言编写的代码 CPPunit:主要测试用C++语言编写的代码 unittest , PyUnit:主要测试用python语言编写的代码 MiniUnit: 主要用于测试C语言编写的代码 |
三、实验步骤
采用测试框架 对自己“结对编程”实验的程序模块(类)进行单元测试。
3.1源码
为了便于进行测试,我对代码进行了调整,将功能划分的更加具体。源代码中,设置地图大小和绘制动图两个功能都在主函数main()中,我将其写成单独的两个函数:setgridsize()、showgird(),将其模块化。
# coding=utf-8 import sys, argparse # argparse是python的一个命令行解析包 import numpy as np # 数组 import matplotlib.pyplot as plt # 生成动画 import matplotlib.animation as animation # 更新模拟 from matplotlib.colors import ListedColormap def randomGrid(rows, clos):# returns a grid of NxM random values vals=[1,0] return np.random.choice(vals, rows * clos, p=[0.2, 0.8]).reshape(rows, clos) # 采用随机的初始状态 # 实现环形边界条件 def update(frameNum, img, grid, rows, clos): newGrid = grid.copy() for i in range(rows): for j in range(clos): # 检测网格的8个边缘。用取模运算符让值在边缘折返 total = int((grid[i, (j - 1) % clos] + grid[i, (j + 1) % clos] + grid[(i - 1) % rows, j] + grid[(i + 1) % rows, j] + grid[(i - 1) % rows, (j - 1) % clos] + grid[(i - 1) % rows, (j + 1) % clos] + grid[(i + 1) % rows, (j - 1) % clos] + grid[(i + 1) % rows, (j + 1) % clos])) if grid[i, j] == 1: if (total < 2) or (total > 3): newGrid[i, j] = 0 else: if total == 3: newGrid[i, j] = 1 # update data img.set_data(newGrid) grid[:] = newGrid[:] return img def setgridsize(): # set grid size print("请输入一个地图的长与宽:") clos= input() rows = input() try: if int(clos) >7 or int(clos)<101: clos= int(clos) else: print('clos out of range!') if int(rows) > 7 and int(rows)<101: rows= int(rows) else: print('rows out of range!') except ValueError: print('the input is not a number!') return clos, rows def showgrid(grid,rows,clos): yeah = ('LightPink', 'black') cmap = ListedColormap(yeah) parser = argparse.ArgumentParser() parser.add_argument('--interval', dest='interval', required=False) # 设置动画更新间隔的毫秒数 args = parser.parse_args() # 设置动画 fig, ax = plt.subplots(facecolor='Lavender') # 配置 matplotlib 的绘图和动画参数 img = ax.imshow(grid, cmap=cmap, interpolation='nearest') # 用plt.show()方法将这个矩阵的值显示为图像,并给 interpolation 选项传入'nearest'值,以得到尖锐的边缘(否则是模糊的) ani = animation.FuncAnimation(fig, update, fargs=(img, grid, rows, clos), frames=10, interval=500, save_count=50) # animation.FuncAnimation()调用函数 update(),该函数在前面的程序中定义,根据 Conway 生命游戏的规则,采用环形边界条件来更新网格。 plt.show() return 'success' # 向程序发送命令行参数 def main(): clos, rows=setgridsize() while(clos == -1 or rows == -1): clos, rows = setgridsize() # declare grid grid = randomGrid(rows, clos) showgrid(grid, rows, clos) # call main if __name__ == '__main__': main()
3.2测试用例设计 (结合单元测试的内容和模块功能设计测试用例)
调整代码后,我的程序主要包含4个模块:设置地图长与宽、初始化地图、更新地图状态、打印地图。
a.设置地图长与宽: setgridsize()
此模块功能是根据提示输入地图的长与宽(M和N)的值,判断输入的数据是否符合要求(我设置的数据范围都是8~100之间),若符合则返回cols、rows。
测试目标 |
用例描述 |
详细信息 |
用例参数 |
预期结果 |
断言设置 |
setgridsize() |
正常用例 |
输入数据为正整数 |
clos=20,rows=30 |
返回clos=20,rows=30 |
1.数据能否转换为整数 2.判断rows与cols是否在(8-100)之间 |
输入数据为浮点数 |
clos=20.4,rows=30.6 |
抛出异常ValueError |
|||
边界用例 |
边界最小值 |
clos=8,rows=8 |
返回clos=8,rows=8 |
||
边界最大值 |
clos=100,rows=100 |
返回clos=100,rows=100 |
|||
错误用例 |
输入数据低于取值范围 |
clos=-15,rows=23 |
报错:clos out of range |
||
输入数据超出取值范围 |
clos=24,rows=190 |
报错:rows out of range |
|||
输入数据为非数字 |
clos=clos,rows=rows |
抛出异常ValueError |
b.初始化地图: randomGrid(rows,cols)
此模块的功能是根据传入的参数rows、cols,返回一个cols*rows的矩阵,且矩阵元素的值只在0和1中。
测试目标 |
用例描述 |
详细信息 |
用例参数 |
预期结果 |
断言设置 |
randomGrid(rows,cols) |
正常用例 |
形参为正整数 |
clos=20,rows=30 |
返回20*30的矩阵,且矩阵中所有元素的值都在0,1之间 |
1.grid是否是大小为20*30的矩阵 2.grid中所有元素是否在0,1之间 |
错误用例 |
实际参数与形参属性不匹配 |
clos=clos,rows=rows |
程序异常,中断运行 |
||
实际参数与形参个数不匹配 |
clos=20,rows=null |
程序异常,中断运行 |
c.更新地图状态: update(frameNum, img, grid, rows,cols)
此模块的功能是根据传入的grid,更新下一状态的grid,并以图像的形式返回该grid。
测试目标 |
用例描述 |
详细信息 |
用例参数 |
预期结果 |
断言设置 |
update(frameNum, img, grid, rows,cols) |
正常用例 |
实际参数与形参都匹配 |
frameNum=10, img, grid, rows=20,cols=30 |
返回20*30的矩阵,且矩阵中所有元素的值都在0,1之间 |
1.grid是否是大小为20*30的矩阵 2.grid中所有元素是否在0,1之间 |
错误用例 |
实际参数与形参属性不匹配 |
rows=20,cols=30,frameNum=10, img, grid, |
程序异常,中断运行 |
||
实际参数与形参个数不匹配 |
frameNum=10, img, grid |
程序异常,中断运行 |
d.打印地图: showgrid(grid,rows,cols)
此模块的功能是以传入的grid为基础,动态打印每一个状态的grid图像。
测试目标 |
用例描述 |
详细信息 |
用例参数 |
预期结果 |
断言设置 |
showgrid(grid,rows,cols) |
正常用例 |
实际参数与形参都匹配 |
grid, rows=20,cols=30 |
动态打印:以grid为初始地图,不断更新的地图 |
返回值是不是success |
错误用例
|
实际参数与形参属性不匹配 |
rows=20,cols=30,grid |
程序异常,中断运行 |
||
实际参数与形参个数不匹配 |
grid,rows=20 |
程序异常,中断运行 |
3.3选择的测试框架介绍、安装过程
由于本次实验的代码是基于python的,所以我选择的是unittest框架。
3.3.1 unittest介绍
Python中有非常多的单元测试框架,如unittest、pytest、nose、doctest等,Python2.1及其以后的版本已经将unittest作为一个标准模块放入Python开发包中(我使用的是python 3.7,所以不用安装unittest,可以直接使用)。unittest是xUnit系列框架中的一员,它们的工作方式都差不多。
unittest 要求单元测试类必须继承 unittest.TestCase,该类中的测试方法需要满足如下要求:
- 测试方法不应该有返回值。
- 测试方法不应该有任何参数。
- 测试方法应以test 开头
每一个测试方法(名称以 test_ 开头的方法)对应一个要测试的函数 / 功能 / 使用场景。
在测试方法里,我们使用断言方法来判断程序功能是否正常。assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常.一般assert后面跟一个条件和一个断言失败信息。断言失败就会抛出AssertionError异常,并且把自定义的断言失败信息打印出来。如要输出断言失败信息,其形式为assert(a,b,[msg="**********"])
3.3.2运行测试用例
方法一:通过代码调用测试用例
a.程序可以通过调用 unittest.main() 来运行当前源文件中的所有测试用例。
b.通过执行 python 测试文件名 命令即可执行所有测试,并输出测试的结果、通过情况、总耗时等信息。
c.在调用测试脚本时添加 -v 参数使测试显示更为详细的信息。
d.当测试用例识别时,失败的测试用例也有清晰说明用例名称以及错误的位置。
方法二:使用unittest模块运行测试用例
a.使用该模块的语法格式:python -m unittest 测试文件(在使用 python -m unittest 命令运行测试用例时,如果没有指定测试用例,该命令将自动查找并运行当前目录下的所有测试用例。)
b.测试用例的划分:建议一个测试类对应一个被测试功能。(我们可以在一个测试文件中定义多个测试类,只要它们遵循测试用例的“规则”。)
3.4测试代码
a.设置地图长与宽: setgridsize()
#!/usr/bin/env python # -*- coding:utf-8 -*- import unittest import numpy as np import HTMLTestRunner from test2 import setgridsize class SetGridTestCace(unittest.TestCase): def setUp(self): self.number=np.arange(8,101) self.clos=self.rows=-1 def tearDown(self): print('test over') def test_setgridsize(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize1(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize2(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize3(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize4(self): self.clos,self.N=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize5(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') def test_setgridsize6(self): self.clos,self.rows=setgridsize() print(self.clos,self.rows) self.assertIn(self.clos,self.number,'clos out of range') self.assertIn(self.rows,self.number, 'rows out of range') if __name__ == '__main__': unittest.main()
b.初始化地图: randomGrid(rows,cols)
#!/usr/bin/env python # -*- coding:utf-8 -*- import unittest import matplotlib.pyplot as plt # 生成动画 from test2 import randomGrid class SetRandGridTestCace(unittest.TestCase): def setUp(self): self.flag = 'true' def tearDown(self): print('test over') def test_randomGrid(self): self.clos=20 self.rows=30 self.grid=randomGrid(self.rows,self.clos) self.assertEqual(self.grid.size,self.rows*self.clos,msg='不是一个20*30的矩阵') for gridvalue in self.grid: for value in gridvalue: if value!=1 and value!=0: self.flag='false' break self.assertEqual(self.flag,'true',msg='细胞生命状态不正确') def test_randomGrid1(self): self.clos='clos' self.rows='rows' self.grid=randomGrid(self.rows,self.clos) self.assertEqual(self.grid.size,self.rows*self.clos,msg='不是一个矩阵') def test_randomGrid2(self): self.clos=20 self.rows='' self.grid=randomGrid(self.rows,self.clos) self.assertEqual(self.grid.size,self.rows*self.clos,msg='不是一个矩阵') if __name__ == '__main__': unittest.main(verbosity=2)
c.更新地图状态: update(frameNum, img, grid, rows,cols)
#!/usr/bin/env python # -*- coding:utf-8 -*- import unittest import matplotlib.pyplot as plt # 生成动画 from matplotlib.colors import ListedColormap from test2 import update,randomGrid class SetUpdateTestCace(unittest.TestCase): def setUp(self): self.grid= randomGrid(9, 9) self.grid1=self.grid.copy() self.yeah = ('LightPink', 'black') self.cmap = ListedColormap(self.yeah) self.fig, self.ax = plt.subplots(facecolor='Lavender') # 配置 matplotlib 的绘图和动画参数 self.img = self.ax.imshow(self.grid, cmap=self.cmap, interpolation='nearest') def tearDown(self): print('test over') def test_update(self): self.img=update(10, self.img, self.grid,9,9) for x,y in zip(self.grid,self.grid1): for i,j in zip(x,y): if i!=j: self.flag='true' break print(self.grid1) print(self.grid) self.assertEqual(self.flag,'false', msg='地图没有更新') def test_update1(self): self.img=update(9,9,10, self.img, self.grid) for x,y in zip(self.grid,self.grid1): for i,j in zip(x,y): if i!=j: i=2 break self.assertEqual(i,2, msg='地图没有更新') def test_update2(self): self.img=update(10, self.img, self.grid) for x,y in zip(self.grid,self.grid1): for i,j in zip(x,y): if i!=j: i=2 break self.assertEqual(i,2, msg='地图没有更新') if __name__ == '__main__': unittest.main(verbosity=2)
d.打印地图: showgrid(grid,rows,cols)
#!/usr/bin/env python # -*- coding:utf-8 -*- import unittest import numpy as np from test2 import showgrid,randomGrid,update class ShowgridTestCace(unittest.TestCase): def setUp(self): self.flag='fail' self.grid = randomGrid(20, 30) def tearDown(self): print('test over') def test_showgrid(self): self.flag=showgrid(self.grid,20,30) self.assertEqual(self.flag,'success', msg='生命游戏运行失败') def test_showgrid1(self): self.flag=showgrid( 20, 30,self.grid) self.assertEqual(self.flag, 'success', msg='生命游戏运行失败') def test_showgrid2(self): self.flag=showgrid(self.grid,30) print("23456") self.assertEqual(self.flag, 'success', msg='生命游戏运行失败') if __name__ == '__main__': unittest.main()
3.5测试结果与分析
a.设置地图长与宽: setgridsize()
测试目标 |
用例描述 |
用例参数 |
实际结果 |
setgridsize() |
正常用例 |
clos=20,rows=30 |
clos=20,rows=30 |
clos=20.4,rows=30.6 |
the input is not a number |
||
边界用例 |
clos=8,rows=8 |
clos=8,rows=8 |
|
clos=100,rows=100 |
clos=100,rows=100 |
||
错误用例 |
clos=-15,rows=23 |
-15 not found in array([ 8,……,100]) | |
clos=24,rows=190 |
rows out of range! |
||
clos=clos,rows=rows |
the input is not a number |
输入7组测试数据:
测试结果:(3个成功,4个失败)
b.初始化地图: randomGrid(rows,cols)
测试目标 |
用例描述 |
用例参数 |
实际结果 |
randomGrid(rows,cols) |
正常用例 |
clos=20,rows=30 |
返回20*30的矩阵,且矩阵中所有元素的值都在0,1之间 |
错误用例 |
clos=clos,rows=rows |
程序异常,中断运行 |
|
clos=20,rows=null |
程序异常,中断运行 |
测试结果:(1个成功,2个失败)
因为返回的矩阵中取值都是在0-1之间,但是结果中没有显示相关信息,所以我们此时不知道是正确的还是测试没有检测出来。我将断言中矩阵元素的取值范围改为(1-2),查看结果知检测是正确的。
c.更新地图状态: update(frameNum, img, grid, rows,cols)
测试目标 |
用例描述 |
用例参数 |
实际结果 |
update(frameNum, img, grid, rows,cols) |
正常用例 |
frameNum=10, img, grid, rows=9,cols=9 |
返回20*30的矩阵,且矩阵中所有元素的值都在0,1之间 |
错误用例 |
rows=9,cols=9,frameNum=10, img, grid, |
程序异常,中断运行 |
|
frameNum=10, img, grid |
程序异常,中断运行 |
测试结果:(1个成功,2个失败)
因为返回的矩阵中取值改变了,但是结果中没有显示相关信息,所以我们此时不知道是正确的还是测试没有检测出来。所以我将更新前后的矩阵都打印出来,并将断言中判断更新的条件改为判断没有更新,查看结果知检测是正确的。
d.打印地图: showgrid(grid,rows,cols)
测试目标 |
用例描述 |
用例参数 |
实际结果 |
showgrid(grid,rows,cols) |
正常用例 |
grid, rows=20,cols=30 |
动态打印:以grid为初始地图,不断更新的地图 |
错误用例
|
rows=20,cols=30,grid |
程序异常,中断运行 |
|
grid,rows=20 |
程序异常,中断运行 |
测试结果:(1个正确,2个错误)
因为成功的用例断言没有显示信息,而失败的用例函数没有执行断言,所以我们此时不知道第一个用例是正确的还是测试没有检测出来。所以我将第一个用例断言中判断更新的条件改为判断没有执行成功,查看结果知检测是正确的。
e.配合HTMLTESTRunner生成测试报告
查阅资料为:https://blog.csdn.net/galen2016/article/details/73251240
根据资料编写测试报告代码为all_test.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- import HTMLTestRunner import unittest import os,time listaa = "E:\study\软件工程\实验结对编程\wyzproject\wyz_lifegame" def createsuite1(): testunit=unittest.TestSuite() discover=unittest.defaultTestLoader.discover(listaa,pattern='test_*.py',top_level_dir=None) for test_suite in discover: for test_case in test_suite: testunit.addTests(test_case) print(testunit) return testunit now = time.strftime("%Y-%m-%d %H_%M_%S",time.localtime()) filename="E:\study\软件工程\实验结对编程\wyzproject\wyz_lifegame\"+now+"_result.html" fp=open(filename,'wb') runner=HTMLTestRunner.HTMLTestRunner( stream=fp, title=u'生命游戏测试报告', description=u'用例执行情况:') runner.run(createsuite1()) #关闭文件流,不关的话生成的报告是空的 fp.close()
运行结果显示为:
结果中可以看出该目录下一共有四个测试用例,根据先后顺序来执行。由于本次实验中只有一个测试用例需要输入数据,所以不用区分顺序,直接输入数据即可。
打开该html文件:
3.6push测试报告和测试代码到各自的github仓库
四、实验遇到的问题及解决
1.我将setUp()函数名写错了,因为在pycharm中打了U的时候P自动出来了,所以我没注意到大小写的问题。
2.函数换行出错。我在写主函数的时候自动换行,所以写下一个函数的时候缩进产生了问题。
3.input输入的默认为字符型,需要用int强制转换。
4.在对返回的img图片进行断言测试时,总是不成功(两个图的内容一样,但对象是不一样的),后来的改了测试对象,直接对矩阵中的元素进行测试。
5测试方法没有以test开头
我在执行test_showgrid.py时,由于忘记测试方法要以test为开头,导致测试结果一直为空,我一开始还以为是由于此函数需要调用其他函数才导致不能成功的。后来我不断尝试发现,即使没有执行函数调用,此测试还是没有执行,重新浏览了一下代码,发现了问题所在,在方法前面加上test后,即可。
6.生成测试报告失败
我根据参考的教程1编写生成测试报告的代码,结果执行之后并没有出现结果,我也找不出了自己的代码有什么错。后来我重新找了教程2,换了一种方式来实现生成测试报告,然后成功了。
其实在实现测试用例的过程中,我出现了各种各样的错误,由于是在不断调试,我也没有及时记录下来,导致后来我忘记了还有哪些问题。
五、思考题:
比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。
工匠一:先拉上一根水平线,砌每一块砖时,都与这根水平线进行比较,使得每一块砖都保持水平。
工匠二:先将一排砖都砌完,然后拉上一根水平线,看看哪些砖有问题,再进行调整。
答:虽然两种做法都有偏向于适合的场景,但我认为第二种较好。
工匠一的做法适合于:工程量较小,要求严格,所以每一个步骤能及时发现问题,能及时解决,每一步骤都会完成的非常好,质量高。
工匠二的做法适合于:工程量较大,要求不是特别严格。所以每个步骤出现的错误不能及时解决,但整个工程效率高,质量也较高。
结合编码和单元测试我们知,一般测试用例会比较多且每个模块下各个测试用例都差不多。所以如果用工匠一的做法,对每个测试方法进行编写代码然后执行,这不仅要会产生很多冗余的代码还会浪费很多时间,测试效率较低。但是用工匠二的做法,对每个模块将所有的测试方法写在一个测试类中,然后一起执行能节省很多时间,效率会高很多。所以我觉得工匠二的做法更好。
六、实验小结
1.本次实验我时间主要花费在了:了解unittest基本使用方法和测试代码的编写。由于是初次学习单元测试,所以我先查阅了相关资料,了解了unittest使用的原理和基本使用方法。在编写测试代码时,由于对断言的使用不是很熟练,所以我花了很长时间对返回的数据进行测试。
2.在根据资料查阅编写生成测试报告中,我一开始写的代码事基于单个测试文件的,但是运行后没有结果产生,我调试了很久也没发现问题所在。之后查找了另外一个教程,代码是基于多个测试用例的,编写后发现可以产生测试结果,而且效率更高。所以有时候,遇到问题且解决不了时,我们可以换个方法去实现。
3.我发现即使是完成此实验后,我对单元测试掌握的还是不够。虽然我已经了解了单元测试的内容,了解了python语言基于unittest框架的工作原理及使用方法,但我发现我在写测试用例的时候还很吃力。当有的函数不是简单的输入输出数据时,我就不知如何对测试预期结果进行设定,比如返回的是图片或没有返回值等,我就转换了思路对函数中实际变化了的数据进行测试。
4.不同编程语言有不同的测试框架,而本次实验我只是用unittest测试框架对python进行了单元测试,这只是单元测试的一个方面。而且实验中我对unittest测试框架的使用也是最基础的,比如说TestSuite、TestRunner执行器、断言的设置都不是很熟练,TestListener过程监听我还没有尝试过。但这只是一个开头,之后的学习中我会加强这方面的学习。
参考资料:
1.unittest框架基本使用: https://blog.csdn.net/weixin_38871478/article/details/90447739
2.unittest测试框架总结 :https://blog.csdn.net/weixin_30945319/article/details/95668383
3.python单元测试详解 : https://blog.csdn.net/yournevermore/article/details/91488532
4.python-生成HTMLTestRunner测试报告 : https://blog.csdn.net/galen2016/article/details/73251240