一、实验目的
1、掌握单元测试的方法
2、学习XUnit测试原理及框架
3、学习使用测试框架进行单元测试的方法和过程
二、实验内容与要求
1、了解单元测试的原理与框架
1.1 单元测试原理
单元测试,是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如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语言编写的代码
1.3 结对编程的小组采用测试框架对自己“结对编程”实验的程序模块(类)进行单元测试,提交单元测试报告。
测试报告包括以下内容:
1)源码
2)测试用例设计 (结合单元测试的内容和模块功能设计测试用例)
3)选择的测试框架介绍、安装过程
4 )测试代码
5)测试结果与分析
1.4 push测试报告和测试代码到各自的github仓库
三、实验过程
1、源码及其仓库
源代码对于四则运算简单算法部分并未进行函数部分,整个函数只包含四则运算算法和输出界面设计两个部分,所以只对包含算法的函数部分进行测试
源码地址:https://github.com/heguohang/pair_programming
源码小组信息: 队长:何国行; 队员:汤友丽;
源码如下:
import random # 四则运算 def szys(): sym = ['+', '-', '×', '÷'] f = random.randint(0, 3) n1 = random.randint(0, 100) n2 = random.randint(0, 100) result = 0 if f == 0: # 加法 result = n1 + n2 if result >= 100: result = szys() return result elif f == 1: # 减法,要先比较大小,防止输出负数 n1, n2 = max(n1, n2), min(n1, n2) result = n1 - n2 if result >= 100: result = szys() return result elif f == 2: # 乘法 result = n1 * n2 if result >= 100: result = szys() return result elif f == 3: # 除法,要比较大小,并循环取整除 n1, n2 = max(n1, n2), min(n1, n2) while n1 % n2 != 0: n1 = random.randint(1, 10) n2 = random.randint(1, 10) n1, n2 = max(n1, n2), min(n1, n2) result = int(n1 / n2) if result >= 100: result = szys() return result if result <= 100: print(n1, sym[f], n2, '= ', end='') return result # 制作题库 def test(): sym = ['+', '-', '×', '÷'] print('输入所需要的题目数量') n = int(input()) result = [] m = 0 while m <= (n - 1): print(m + 1, end='、') result.append(szys()) print(' ') m = m + 1 m = 0 print('对应的答案:') while m <= (n - 1): print(m + 1, '、', result[m]) m = m + 1 print('欢迎使用在线四则运算系统') print('选择想要的模式') print('1、进行四则运算') print('2、制作题库') n = int(input()) # 当输入1时,进行四则运算,调用函数syzs() if n == 1: a = 0 x = 1 print('输入题目数量') b = int(input()) while x <= b: print('第', x, '题') result = szys() j = input() s = int(j) if s == result: print('right') a = a + 1 else: print('error.,the answer is', result) print('一共答对', a, '题') x = x + 1 print('-----------再见-------------') # 当输入2时,进行制作题库 if n == 2: test()
2、测试用例设计
由于并未将算法的加减乘除划分为不同模块,所以在测试用例的设计上只对算法函数整体进行测试。
函数实现了四则运算功能,随机生成四则运算题目并返回正确答案,因此设置如下测试:
-函数返回值即题目正确答案是否在0-100范围内;
-函数返回值即题目正确答案是否是整数不包含分数
-函数返回值即题目正确答案是否为空;
-函数返回值即题目正确答案是否满足随机性(不可为特定答案);
3、软件配置及测试内容
项目源码为使用pycharm编写的python程序,通过网上查阅资料得知pycharm可以对python代码进行单元测试,通过进一步查阅得知具体步骤。
打开pycharm,新建项目和python包,将结对编程实验代码复制其中,在同一目录下创建测试代码cs.py,选择代码种类为python unit test,如下:
测试代码cs.py系统生成文件如下:
查阅资料得知,在pycharm上进行单元测试之前需先配置HTMLTestRunner.py文件,该文件下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html,下载完成后将该文件放入python安装目录Lib文件夹下即可,
开始编写测试代码,注意引入下载的HTMLTestRunner模块,引入源代码,并将包含测试内容的python包标记为源文件夹
完成这些配置后即可进行正式的测试代码编写,根据测试用例设计完成了测试代码的编写,如下:
import unittest import HTMLTestRunner import testfile class fileTest(unittest.TestCase): @classmethod def setUpClass(self): pass @classmethod def tearDownClass(self): pass def setUp(self): pass def tearDown(self): pass def test_jg(self): print('test_jg:') rv = testfile.szys() assert 0 <= rv <= 100 #测试题目结果是否在0到100以内 def test_zs(self): print('test_zs:') rv = testfile.szys() self.assertIsInstance(rv,int) #测试题目答案是否是整数 def test_k(self): print('test_k:') rv = testfile.szys() self.assertIsNotNone(rv) #测试函数返回值是否为空 def test_n(self): print('test_n:') rv = testfile.szys() self.assertEqual(rv,10) #测试函数返回值为具体某数,因为题目是随机产生,所以本条测试在大部分测试下都应不通过 if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(fileTest("test_jg")) suite.addTest(fileTest("test_zs")) suite.addTest(fileTest("test_k")) suite.addTest(fileTest("test_n"))
和运行普通代码一样运行测试代码文件,无需额外操作,测试结果如下:
查看测试未通过的部分
不为特定答案10,所以测试未通过,则四则运算答案不为特定答案,满足题目输出的随机性,即为未通过测试合理;其他三项测试根据测试用例设计全部通过。
导出测试报告,选择测试报告为HTML文件,在文件夹下可以看到报告生成并可以在浏览器中打开,如下:
4、push测试报告和测试代码到github仓库
仓库地址: https://github.com/heguohang/pair_programming
仓库截图:
四、思考题
比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。
答:我认为工匠一的做法更好;结合单元测试来看,工匠一的做法是开发前测试先行,即开发前就把测试考虑清楚,测试驱动开发,以保证开发的每一部分都有理有据,不会出现重大的错误;工匠二的做法带有一定风险性,如果开发部分做的不尽人意,在进行测试时可能会有崩溃的风险。
五、小结
通过本次实验,我了解了python单元测试的一些基本操作步骤和方法;在进行实验的过程中我学习了测试相关原理和框架;知道了单元测试对于开发程序的重要性;让我在以后的学习生活中提高了对这方面的关注,对我有很大帮助。