接着前面自动化测试的测试用例生成和收集,下面我们就需要做的是对测试用例进行排序,简单来说就是达到语句的全部覆盖。说到排序就涉及到三个算法,下面就是我对三个算法的阐述及代码实现。咱们由易入难。
1’自动用例生成(使用Randoop)>
2‘评价(对用例筛选冗余)>功能覆盖、语句覆盖(一般用后者)
>插桩 (插入语句)
用Javassist实现自动插入语句
3’测试用例排序排序>三种算法实现测试用例排序
1‘ 随机算法:测试用例会排序就是生成一个用例序列,随机算法就是随机生成一个测试用例序列
下面是对序列进行打乱随机排序的两种方法:
//(1)利用math的random方法
List l=new ArrayList( input); //将input数组放入arraylist,input为集合 List res=new ArrayList(); //用来存放随机产生元素的结果 Random r=new Random();//随机数 int size=l.size(); for(int i=0;i<size;i++){ res.add(l.remove( r.nextInt(l.size()))); //为了保证不重复,每次随机产生后都删除该元素。 } //此时res中存放的就是随机排序的结果。
//(2)利用collections的buffle方法
import java.util.Collections; import java.util.LinkedList; import java.util.List; public class Test {
List list = new LinkedList(); public static void main(String[] args) { List list = new LinkedList(); for ( int i = 0 ; i < 9 ; i ++ ) { list.add( " a " + i); } Collections.sort(list); // 顺序排列 System.out.println(list); Collections.shuffle(list); //随机打乱一个序列 System.out.println(list); Collections.reverse(list); // 倒序排列 System.out.println(list); System.out.println(Collections.binarySearch(list, " a5 " )); // 折半查找 }
对此我简单的用list输入一个测试用例序列并进行随机排序,输出结果:
package testpaixu; import java.util.*; public class randomA { public static void main(String args[]){ //测试用例数目 int N=6; List<Integer> lis=new ArrayList<Integer>(); //添加测试用例1,2,3,4,5,6为序列 for(int i=1;i<=N;i++){ lis.add(i); } //输出原测试用例序列 System.out.println("测试用例序列:"+lis); //随机排序 Collections.shuffle(lis); System.out.println("随机排序后测试用例序列:"+lis); } }
结果显示:
2’· 贪心算法:
一种能够得到某种度量意义下的最优解的分级处理方法,它总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解算法。
该算法存在问题:
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
现在我用一个简单的覆盖矩阵进行代码实现:
首先我在F:\目录下创建了一个txt文件,文件名 greedyA.txt,内容如下:
(横向为四个测试用例,纵向为5个覆盖语句,0为未覆盖,1为已覆盖)
首先我们先整体上对排序的逻辑过程进行考量一下,先从F:\greedyA.txt目录下读取文件,并置入数组,然后对每个测试用例进行相加,数字最大的为覆盖范围最大的,将此加入新的数组,并将该测试用例之和重置为-1,以此类推,直到序列排序完成。
下面进行代码实现:
package testpaixu; import java.io.*; import java.util.*; //贪婪算法 public class greedyA { public static void main(String args[]){ //test number int tn=4; String[] test=new String[tn]; //下标为测试用例-1,数字为测试用例覆盖范围 int[] sum=new int[tn]; for(int i=0;i<sum.length;i++){ sum[i]=0; //初始化为0 } //读取测试用例覆盖矩阵 String filePath="F:\greedyA.txt"; try { String encoding="GBK"; File file=new File(filePath); //判断文件是否存在 if(file.isFile() && file.exists()){ //考虑到编码格式 InputStreamReader read = new InputStreamReader( new FileInputStream(file),encoding); BufferedReader br = new BufferedReader(read); String lineTxt = null; System.out.println("测试用例覆盖矩阵(横向测试用例,纵向覆盖语句(“0”未覆盖,“1”已覆盖)):"); while((lineTxt =br.readLine()) != null){ //分解覆盖矩阵 test=lineTxt.split(" "); System.out.println(test[0]+test[1]+test[2]+test[3]); //每个测试用例的覆盖范围 sum[0]=sum[0]+Integer.parseInt(test[0]); sum[1]=sum[1]+Integer.parseInt(test[1]); sum[2]=sum[2]+Integer.parseInt(test[2]); sum[3]=sum[3]+Integer.parseInt(test[3]); } read.close(); }else{ System.out.println("找不到指定的文件"); } } catch (Exception e) { System.out.println("读取文件内容出错"); e.printStackTrace(); } System.out.println("测试用例1覆盖范围:"+sum[0]); System.out.println("测试用例2覆盖范围:"+sum[1]); System.out.println("测试用例3覆盖范围:"+sum[2]); System.out.println("测试用例4覆盖范围:"+sum[3]); //使用贪婪算法对测试用例集排序 List<Integer> ls=new ArrayList<Integer>(); //初始化贪婪算法后序列 int[] tests=new int[tn]; for(int i=0;i<tests.length;i++){ tests[i]=i+1; //测试用例集初始序列 } int index=0; int max=sum[0]; for(int j=0;j<sum.length;j++){ //获取数组最大 for(int i=0;i<sum.length;i++){ if(sum[i]>max){ max=sum[i]; index=i; } } ls.add(tests[index]); sum[index]=-1; max=sum[index]; } System.out.println("测试用例集排序后序列: "+ls); } }
运行结果:
3‘ 额外贪心:迭代的选取覆盖实体最多的实例,然后对于剩下的测试用例调整覆盖信息,把被选取的测试用例标记为“覆盖的”,重复此过程直至测试实体集中所有的实例都被标记为“覆盖的”。
算法实现思想:对测试用例集的覆盖与未覆盖标记为“1”和“0”,获取测试用例集原始序列,对各测试的分布进行相加,结果最大的编入结果序列list2。起始设定两个序列list,一个全部为0记为slst,一个全部为1记为slst2。在求和获得最大的测试进行修改,将此项为1的全部编入slst;然后将slst中为1的对应项对所有测试用例集的对应项修改,将他们全部改为0(以便循环相加求和时不会冲突),以此循环直到slst==slst2为true。
到此,还有一步完善,将结果序列与原始序列相比较,先前未排序的加入到结果序列末端。
下面进行编译实现,同第二种算法一样,先于extragreedyA.txt中添加一个覆盖矩阵(横向为测试用例序列,纵向为覆盖语句序列),覆盖矩阵如下:
代码实现如下:
package testpaixu; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.*; //额外贪心算法 public class extragreedyA { public static void main(String args[]){ //test number int tn=4; //覆盖语句数量 int sn=5; String[] test=new String[tn]; //下标为测试用例-1,数字为测试用例覆盖范围 int[] sum=new int[tn]; for(int i=0;i<sum.length;i++){ sum[i]=0; //初始化为0 } //测试用例的覆盖语句分布 ArrayList<Integer> lst1=new ArrayList<Integer>(); ArrayList<Integer> lst2=new ArrayList<Integer>(); ArrayList<Integer> lst3=new ArrayList<Integer>(); ArrayList<Integer> lst4=new ArrayList<Integer>(); List<ArrayList<Integer>> lst=new ArrayList<ArrayList<Integer>>(); lst.add(lst1); lst.add(lst2); lst.add(lst3); lst.add(lst4); //初始化覆盖均为0,及均未覆盖 ArrayList<Integer> slst=new ArrayList<Integer>(); for(int i=0;i<sn;i++){ slst.add(0); } System.out.println("初始化语句均为未覆盖:"+slst); //最后覆盖语句均为1 ArrayList<Integer> slst2=new ArrayList<Integer>(); for(int i=0;i<sn;i++){ slst2.add(1); } //读取测试用例覆盖矩阵 String filePath="F:\extragreedyA.txt"; try { String encoding="GBK"; File file=new File(filePath); //判断文件是否存在 if(file.isFile() && file.exists()){ //考虑到编码格式 InputStreamReader read = new InputStreamReader( new FileInputStream(file),encoding); BufferedReader br = new BufferedReader(read); String lineTxt = null; System.out.println("测试用例覆盖矩阵(横向测试用例,纵向覆盖语句(“0”未覆盖,“1”已覆盖)):"); while((lineTxt =br.readLine()) != null){ //分解覆盖矩阵 test=lineTxt.split(" "); System.out.println(test[0]+test[1]+test[2]+test[3]); //每个测试用例的覆盖范围 sum[0]=sum[0]+Integer.parseInt(test[0]); sum[1]=sum[1]+Integer.parseInt(test[1]); sum[2]=sum[2]+Integer.parseInt(test[2]); sum[3]=sum[3]+Integer.parseInt(test[3]); //每个测试用例覆盖语句分布 lst.get(0).add(Integer.parseInt(test[0])); lst.get(1).add(Integer.parseInt(test[1])); lst.get(2).add(Integer.parseInt(test[2])); lst.get(3).add(Integer.parseInt(test[3])); } read.close(); }else{ System.out.println("找不到指定的文件"); } } catch (Exception e) { System.out.println("读取文件内容出错"); e.printStackTrace(); } //测试用例覆盖矩阵 for(int i=0;i<tn;i++){ System.out.println("测试用例"+(i+1)+"覆盖范围:"+sum[i]+"分布:"+lst.get(i)); } //使用额外贪婪算法对测试用例集排序 ArrayList<Integer> ls=new ArrayList<Integer>(); //初始化贪婪算法后序列 int[] lstsum; int add; int index; int max; boolean b=true; while(b){ //获得每个测试用例的覆盖范围 add=0; lstsum=new int[tn]; for(int m=0;m<lst.size();m++){ for(int n=0;n<lst.get(m).size();n++){ int j=(Integer)lst.get(m).get(n); add+=j; } lstsum[m]=add; add=0; } //获取覆盖范围最大的测试用例 index=0; max=lstsum[0]; //获取数组最大 for(int i=0;i<lstsum.length;i++){ if(lstsum[i]>max){ max=lstsum[i]; index=i; } } ls.add((index+1)); //将初始序列覆盖范围改掉 for(int i=0;i<slst.size();i++){ if(lst.get(index).get(i)==1){ slst.set(i, 1); } } //将该序列覆盖的均改为0,并延至所有序列 for(int i=0;i<lst.size();i++){ for(int j=0;j<lst.get(i).size();j++){ if(slst.get(j)==1){ lst.get(i).set(j, 0); } } } max=-1; for(int i=0;i<slst.size();i++){ if(slst.get(i)!=slst2.get(i)){ b=true;break; } b=false; } } //如果排序后还有测试用例没有排序,要对tests[]的剩余进行添加 ArrayList<Integer> tests=new ArrayList<Integer>(); for(int i=0;i<tn;i++){ tests.add(i+1); } System.out.println("测试用例集排序前序列: "+tests); for(int i=0;i<tests.size();i++){ if(!(ls.contains(tests.get(i)))){ ls.add(tests.get(i)); } } System.out.println("测试用例集排序后序列: "+ls); } }
显示结果:
总结:
自动化测试的整体步骤如下(目标为一个程序a.java):
1‘先是利用randoop工具对程序自动生成测试用例,得到测试用例集文件(tests.java文件),并撰写生成报告(包含日期时间,原程序名称,测试数量及概述,操作时间等);
2’其次,对源程序进行插桩(利用Javassist进行方法,语句插桩)得到文件(a.class,与原a.class文件不同,目录不在bin文件夹内);
3’利用Java Runtime.exec()方法对测试用例集文件进行编译得到tests.class文件。
(该方法使用方法博客网址: http://www.cnblogs.com/mingforyou/p/3551199.html )
运行tests.class查看每个测试用例插桩结果:
a 获取覆盖信息(覆盖哪些语句,方法)并导入一个txt文件中方便查看、
b 各个用例的覆盖率(方便用贪心算法和随机算法)、
c 各个用例的覆盖分布(覆盖于未覆盖),已覆盖矩阵的形式导入txt文件中,以便运用额外贪心算法进行排序。
并撰写收集报告(包含日期时间,源程序名称,测试用例集名称,以及该集合的覆盖率矩阵,操作时间等);
4‘排序(三种算法),读取上一个步骤形成的两个txt文件,对覆盖率或者覆盖分布矩阵进行排序;
5’收集排序结果,并撰写排序报告(包含日期时间,源程序名称,测试用例集名称,排序实现程序名称,测试用例集的覆盖分布,测试用例集的结果序列等)。
题外话,过此我每个流程都进行了粗糙的代码实现,因此会对一个完整的程序进行完整的流程实现(自动生成测试用例,收集覆盖矩阵,排序,排序结果输出)。