一、实验目的
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语言编写的代码
2、结对编程的小组采用测试框架 对自己“结对编程”实验的程序模块(类)进行单元测试
1)源码
1 package sizeyusuan; 2 3 import java.util.*; 4 import java.io.BufferedReader; 5 import java.io.BufferedWriter; 6 import java.io.File; 7 import java.io.FileOutputStream; 8 import java.io.FileReader; 9 import java.io.FileWriter; 10 import java.io.IOException; 11 import java.io.OutputStreamWriter; 12 import java.io.PrintWriter; 13 import java.io.RandomAccessFile; 14 public class xiaoxuesheng { 15 private static Random random = new Random(); 16 public static int range; 17 public static String reductionofFraction(int a, int b) {// 分数约分,用于计算结果 18 int y = 1; 19 for (int i = a; i >= 1; i--) { 20 if (a % i == 0 && b % i == 0) { 21 y = i; 22 break; 23 } 24 } 25 int z = a / y;// 分子 26 int m = b / y;// 分母 27 if (z == 0) { 28 return "0"; 29 } 30 if(m==1) return z+""; 31 else return biaodashi(z,m); 32 33 } 34 public static String biaodashi(int a,int b) {//判断假分数,并化假分数为带分数 35 if(a>=b) { 36 int c; 37 c=a/b; 38 int d; 39 d=a%b; 40 {if(d==0) {return c+"";} 41 return c+"'"+d+"/"+b;} 42 }return a+"/"+b; 43 } 44 45 public static void main(String[] args){ 46 Scanner sc= new Scanner(System.in); 47 System.out.println("请输入产生几以内的数字:"); 48 range=sc.nextInt(); 49 System.out.println("请输入产生多少个运算表达式:"); 50 int num=sc.nextInt(); 51 int rightcount[]=new int[num+2]; 52 int wrongcount[]=new int[num+2]; 53 int right1=0; 54 int wrong1=0; 55 String[] results=new String[num];int i; 56 for( i=0;i<num;i++){ 57 58 String expArr[]=new String[2];//定义生成的题目 59 int a= (int) (random.nextInt(range));//分子 60 int b= (int) (random.nextInt(range));//分母 61 int c= (int) (random.nextInt(range));//另一个分子 62 int d= (int) (random.nextInt(range));//另一个分母 63 int fuhao;//运算符 64 fuhao= (int) (random.nextInt(4)); 65 if(b!=0&&d!=0) {//分母均不为0时生成带有分数的计算题,同时计算结果 66 if(fuhao==0) { 67 int fenzi=a*d+b*c; 68 int fenmu=b*d; 69 expArr[0]=biaodashi(a,b)+'+'+biaodashi(c,d)+'='; 70 System.out.println(expArr[0]); 71 results[i]=reductionofFraction(fenzi, fenmu); 72 73 } 74 if(fuhao==1&&a*d-b*c>=0) { 75 int fenzi=a*d-b*c; 76 int fenmu=b*d; 77 expArr[0]=biaodashi(a,b)+'-'+biaodashi(c,d)+'='; 78 System.out.println(expArr[0]); 79 results[i]=reductionofFraction(fenzi, fenmu); 80 81 } 82 if(fuhao==1&&a*d-b*c<0) { 83 int fenzi=b*c-a*d; 84 int fenmu=b*d; 85 expArr[0]=biaodashi(a,b)+'-'+biaodashi(c,d)+'='; 86 System.out.println(expArr[0]); 87 results[i]=reductionofFraction(fenzi, fenmu); 88 89 } 90 if(fuhao==2) { 91 int fenzi=a*c; 92 int fenmu=b*d; 93 expArr[0]=biaodashi(a,b)+'×'+biaodashi(c,d)+'='; 94 System.out.println(expArr[0]); 95 results[i]=reductionofFraction(fenzi, fenmu); 96 97 } 98 if(fuhao==3&&c!=0) { 99 int fenzi=a*d; 100 int fenmu=b*c; 101 expArr[0]=biaodashi(a,b)+'÷'+biaodashi(c,d)+'='; 102 System.out.println(expArr[0]); 103 results[i]=reductionofFraction(fenzi, fenmu); 104 105 } 106 if(fuhao==3&&c==0) { 107 break; 108 /*c=1; 109 int fenzi=a*d; 110 int fenmu=b*c; 111 expArr[0]=biaodashi(a,b)+'÷'+biaodashi(c,d)+'='; 112 System.out.println(expArr[0]); 113 results[i]=reductionofFraction(fenzi, fenmu);*/ 114 115 } 116 117 } 118 else {//分母至少一个为0时生成只含有整式的运算式,同时计算结果 119 b=1; d=1; 120 if(fuhao==0) { 121 int fenzi=a*d+b*c; 122 int fenmu=b*d; 123 expArr[0]=a+"+"+c+"="; 124 System.out.println(expArr[0]); 125 results[i]=reductionofFraction(fenzi, fenmu); 126 127 } 128 if(fuhao==1&&a*d-b*c>=0) { 129 int fenzi=a*d-b*c; 130 int fenmu=b*d; 131 expArr[0]=a+"-"+c+"="; 132 System.out.println(expArr[0]); 133 results[i]=reductionofFraction(fenzi, fenmu); 134 135 } 136 if(fuhao==1&&a*d-b*c<0) { 137 int fenzi=b*c-a*d; 138 int fenmu=b*d; 139 expArr[0]=c+"-"+a+"="; 140 System.out.println(expArr[0]); 141 results[i]=reductionofFraction(fenzi, fenmu); 142 143 } 144 if(fuhao==2) { 145 int fenzi=a*c; 146 int fenmu=b*d; 147 expArr[0]=c+"×"+a+"="; 148 System.out.println(expArr[0]); 149 results[i]=reductionofFraction(fenzi, fenmu); 150 151 } 152 if(fuhao==3&&c!=0) { 153 int fenzi=a*d; 154 int fenmu=b*c; 155 expArr[0]=a+"÷"+c+"="; 156 System.out.println(expArr[0]); 157 results[i]=reductionofFraction(fenzi, fenmu); 158 159 } 160 if(fuhao==3&&c==0) { 161 break; 162 /*c=1; 163 int fenzi=a*d; 164 int fenmu=b*c; 165 expArr[0]=a+"÷"+c+"="; 166 System.out.println(expArr[0]); 167 results[i]=reductionofFraction(fenzi, fenmu);*/ 168 169 } 170 171 } 172 FileWriter fw = null; 173 try { 174 175 File f=new File("Exersies.txt");//题目写入 176 fw = new FileWriter(f, true); 177 } catch (IOException e) { 178 e.printStackTrace(); 179 }if(expArr[0]!=null) { 180 PrintWriter pw = new PrintWriter(fw); 181 pw.println(i+1+"."+expArr[0]); 182 pw.flush(); 183 try { 184 fw.flush(); 185 pw.close(); 186 fw.close(); 187 } catch (IOException e) { 188 e.printStackTrace(); 189 }}FileWriter fn = null; 190 try { 191 192 File f=new File("Answer.txt");//答案写入 193 fn = new FileWriter(f, true); 194 } catch (IOException e) { 195 e.printStackTrace(); 196 }if(expArr[0]!=null) { 197 PrintWriter pn = new PrintWriter(fn); 198 pn.println(i+1+"."+results[i]); 199 pn.flush(); 200 try { 201 fn.flush(); 202 pn.close(); 203 fn.close(); 204 } catch (IOException e) { 205 e.printStackTrace(); 206 }} 207 } 208 System.out.println("输入ok提交!"); 209 Scanner sc1=new Scanner(System.in); 210 String submit=sc1.nextLine(); 211 if(submit.equals("ok")){ 212 String array[]=new String[num]; 213 try 214 { int k=0; 215 216 FileReader fr = new FileReader("H://eclipse2//eclipse3//sizeyusuan//Your_answers.txt"); 217 BufferedReader br = new BufferedReader(fr); 218 String s ; 219 while((s = br.readLine())!=null) {//读取小学生的答案 220 array[k]=s; k++; 221 }br.close(); 222 fr.close(); 223 }catch(IOException e){ 224 System.out.println("指定文件不存在"); 225 } 226 for(int j=0;j<num;j++){ 227 if(array[j].equals(results[j])) {//验证答案,统计正确和错误的个数 228 229 rightcount[j]=j+1; 230 right1++; 231 } 232 else { 233 234 wrongcount[j]=j+1; 235 wrong1++; 236 } 237 } 238 FileWriter fg = null; 239 try { 240 //反馈正确与错误题目的信息 241 File f=new File("Grade.txt"); 242 fg = new FileWriter(f, true); 243 } catch (IOException e) { 244 e.printStackTrace(); 245 } 246 PrintWriter pg = new PrintWriter(fg); 247 pg.println(" "); 248 pg.print("Correct:"+right1+"("); 249 for (int j = 0; j <= num; j++) { 250 if (rightcount[j] != 0) { 251 pg.print(rightcount[j] + ","); 252 } 253 } 254 pg.println(")"); 255 pg.print("Wrong:"+wrong1+"("); 256 for (int j = 0; j <= num; j++) { 257 if (wrongcount[j] != 0) { 258 pg.print(wrongcount[j] + ","); 259 } 260 } 261 pg.print(")"); 262 pg.flush(); 263 try { 264 fg.flush(); 265 pg.close(); 266 fg.close(); 267 } catch (IOException e) { 268 e.printStackTrace(); 269 }} 270 } 271 }
2)测试用例设计 (结合单元测试的内容和模块功能设计测试用例)
新建一个名为sunxiya 的项目,在其中编写一个 xiaoxuesheng 类。开发一个自动生成小学四则运算题目的命令行 “软件”。实现 输入你要出题的个数,随机产生四则运算,然后用户回答,并且进行打分。规则:用随机数实现100以内的加、减、乘、除运算,其中和与积不能超过100,差不为负(即须大减小),商不为小数或分数(即必须整除)。要求总计输出10个运算式,每输出一个运算式,等待输入结果,然后进行对错判断并输出。最后输出统计答对的题数与分然后对这些功能进行单元测试。
3)安装Junit
将 JUnit4单元测试包引入这个项目,在属性窗口添加Junit,选择Junit4
4)创建测试用例
生成JUnit测试框架,在Eclipse的Package Explorer中用右键点击该类弹出菜单,选择"JUnit测试用例"。在弹出的对话框中,进行相应的选择
5)测试代码
点击“下一步”后,系统会自动列出你这个类中包含的方法,选择你要进行测试的方法。此例中,我们仅对下图所勾选的选项进行测试。
之 后 系 统 会 自 动 生 成 一 个 新 类CalculatorTest,里面包含一些空的测试用例。
6)测试结果
共进行了 3个测试,其中3个测试失败
3、push测试报告和测试代码到自己的github仓库
思考题:
比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。
以单元测试的想法来看,工匠二的做法更好,相当于是在编写程序时设计多个函数,对每个函数进行测试更方便找出代码的不足之处,如果只写一个函数不方便测试。不容易找出测试的切入点。其实每个工匠的做法都有各自的优点。
实验小结
此次实验用的编译器在进行各种操作时比较方便。本次实验找出来的bug虽然不会影响程序的运行,但是会影响测试的速度,存在着潜在隐患,对测试人员和开发人员产生一定的影响。我们要对代码进行重构和优化,使其更加规范。