软件测试第四周作业 WordCount优化

Posted anry-hu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件测试第四周作业 WordCount优化相关的知识,希望对你有一定的参考价值。

Github地址

https://github.com/husterC/WordCountGroupwork

PSP表格

PSP2.1

PSP阶段

预估耗时

(分钟)

实际耗时

(分钟)

Planning

计划

   

· Estimate

· 估计这个任务需要多少时间

540  780

Development

开发

   

· Analysis

· 需求分析 (包括学习新技术)

 60  120

· Design Spec

· 生成设计文档

 30      10

· Design Review

· 设计复审 (和同事审核设计文档)

 30  10

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

 30  50

· Design

· 具体设计

 60  60

· Coding

· 具体编码

 60  60

· Code Review

· 代码复审

 60  90

· Test

· 测试(自我测试,修改代码,提交修改)

 60  120

Reporting

报告

   

· Test Report

· 测试报告

 60  180

· Size Measurement

· 计算工作量

 30  20

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

 60  60
 

合计

 540  780


基本任务

一、简介

  我们小组将整个功能分为四个模块:输入,计算频率,排序,输出。输入:从文件中读数据,并且将单词分割出来,得到字符串数组传入下一个模块,比如读到的数据为“aaa[]ccc;;ccc",那么会得到一个数组为{aaa,ccc,ccc}。计算频率:得到数组后,计算每个单词出现的次数,并且重新整合成一个二维数组,传入下一个模块,比如:将数组变为二维数组{aaa,1,ccc,2}。排序:得到二维数组后,按照要求进行排序,重新整合成一个新的二维数组,传入下一个模块,比如:将二维数组变为新的二维数组{ccc,2,aaa,1}。输出:得到二维数组后,将结果按照要求输出到文件。

二、四个模块的设计

public static ArrayList<String> Input(File);//输入
public static String[][] WordFrequency(ArrayList<String>);//计算频率
public static String[][] ListSort(String[][]);//排序
public static void Output(String [][]);//输出

我需要完成的是第三个模块:排序。

三、接口实现

解题思路

  由获得的二维数组可知,一部分保存数据,一部分保存出现的次数。直接对其进行插入排序,交换的依据是:首先比较次数大小,如果能交换便交换,如果不能,则比较次数是否一样,如果一样,则比较单词大小进行交换。

关键代码:插入排序

      for(i=1;i<list.length;i++) {
            temp2 = list[i][1];
            tp = Integer.parseInt(list[i][1]);
            temp = list[i][0];
            for(j=i-1;j>=0;j--) {
                if(tp>Integer.parseInt(list[j][1])) {
                    list[j+1][0] = list[j][0];
                    list[j+1][1] = list[j][1];
                }
                else if(tp==Integer.parseInt(list[j][1])) {
                    if(temp.compareTo(list[j][0])<0) {
                        list[j+1][0] = list[j][0];
                        list[j+1][1] = list[j][1];
                    }
                    else {
                        break;
                    }
                }
                else {
                    break;
                }
            }
            list[j+1][0] = temp;
            list[j+1][1] = temp2;
        }

四、测试用例的设计

解题思路

  利用Junit4来设计测试用例,我用一个字符串来保存输出结果,比如:排序好的数组为{aaa,2,bbb,1},保存结果的字符串为"aaa2bbb1",用这个字符串与期望结果进行比较,相同便成功。这里采用多参数的方法来解决,避免写很多个测试函数。

关键代码:获取多个测试数据,完成多参数功能

@Parameters
public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][]{

关键代码:测试函数,完成测试的功能

  @Test    
    public void testing() throws Exception{
        result1 = param.split("\\\\,");
        int i,j;
        if(result1.length%2 == 0) {
            res = new String[result1.length/2][2];
        }
        else {
            res = new String[result1.length/2+1][2];
        }
        for(i=0;i<result1.length/2;i++) {
            for(j=0;j<2;j++) {
                res[i][j] = result1[i*2+j];
            }
        }
        listSort.ListSort(res);
        
        assertEquals(result, listSort.result);
    }

(1)基本路径测试法

控制流图

解释一下:首先进行数组长度判断,如果为奇数,肯定出错,因为一个数据对应一份数量。接着往下,如果数组为空,直接返回,如果不为空,开始进行插入排序。插入排序有三种情况,其中两种是进行交换,一种是不交换,所以综上,可以得出控制流图如上。

圈复杂度

12-9+2=5,所以圈复杂度为5,测试用例的数量应该为5

测试用例

//基本路径测试
{"aaa,1,aaa", null},//数组为奇数
{"", null},//数组为空
{"aaa,2,bbb,1", "aaa2bbb1"},//没有交换
{"aaa,1,bbb,2", "bbb2aaa1"},//第一种交换法
{"bbb,1,aaa,1", "aaa1bbb1"},//第二种交换法

运行结果

(2)强健壮等价类测试法

测试用例

 有效类里面有三种情况,无效类有两种:分别是数组为空和数组长度为奇数的情况,所以总共有5个测试用例

//强健壮等价类
{"aaa,1,aaa", null},//数组为奇数
{"", null},//数组为空
{"aaa,2,bbb,1", "aaa2bbb1"},//没有交换
{"aaa,1,bbb,2", "bbb2aaa1"},//第一种交换法
{"bbb,1,aaa,1", "aaa1bbb1"},//第二种交换法

运行结果

(3)错误推测法

一:当字符串数组中有元素为空字符串时,仍然可以进行排序,这里把空字符串当成一个字符串,不会报错。

二:当有重复数据出现时,不会报错,仍然可以进行排序,因为这是上一部分需要完成的功能。

三:尝试增加数组长度,看是否会出错。

测试用例

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
            //错误推测法
            {"aaa,1,,3", "3aaa1"},
            {"aaa,2,aaa,3","aaa3aaa2"},
            {"a,1,b,2,c,3","c3b2a1"},
            {"a,1,b,2,c,3,d,4","d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,6","e6d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,5,f,6","f6e5d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,5,f,6,g,7","g7f6e5d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,5,f,6,g,7,h,8","h8g7f6e5d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,5,f,6,g,7,h,8,i,9","i9h8g7f6e5d4c3b2a1"},
            {"a,1,b,2,c,3,d,4,e,5,f,6,g,7,h,8,i,9,j,100","j100i9h8g7f6e5d4c3b2a1"},

运行结果

(4)测试设计表

(5)单元测试总结

  单元测试质量较好,因为通过了所有的测试,所以被测模块的质量较高。划分的等价类没有边界,所以无法使用边界值测试法,因果图测试法与基本路径测试法类似,其余等价类测试法与强健壮等价类测试法类似,所以不再一一列出。本模块所需要测试的地方较少,因为排序才是重点,默认从上一部分能接收到正确的数据。

(6)小组贡献率

  经过小组成员的协商和具体的工作量,最后决定贡献划分:17045 0.21 17054 0.40 17059 0.21 17060 0.18 

扩展任务

一、对规范的理解

我们组选用的是Java,所以参考《阿里巴巴Java开发手册》

对规范的理解

实例体会

代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束

如果我要命名两个name变量,第二个可以为name_2,但不能直接是name_

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式

如果用拼音的话,不方便他人查看,自己回过来看时,可能也会忘记是什么意思

类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO 等

类名开头需要大写,并且后面每接一个单词都要大写开头

方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式

开头需要小写,后面每接一个单词需要大写开头

常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚

常量命名为MAX_COUNT是错误的,虽然书写格式正确,但是语义不清楚,应为MAX_STOCK_COUNT

类型与中括号紧挨相连来定义数组

int[] array

POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。POJO类中布尔类型的变量不能加is前缀,isLetter定义是错误的,可以改为letterIs

如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式

我实现的模块是排序,所以方法直接命名为listSort()

大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则: 

1) 左大括号前不换行。 

2) 左大括号后换行。 

3) 右大括号前换行。 

4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。

if(***) {

   ***

}else{

   ***

}

 

func(){};

左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格

反例:if (空格a == b空格)

if/for/while/switch/do 等保留字与括号之间都必须加空格

if空格(***)

任何二目、三目运算符的左右两边都需要加一个空格

a空格=空格4

注释的双斜线与注释内容之间有且仅有一个空格

// 注释语句  (注意//后有一个空格)

单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则: 

1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。 

2) 运算符与下文一起换行。 

3) 方法调用的点符号与下文一起换行。 

4) 方法调用时,多个参数,需要换行时,在逗号后进行。 

5) 在括号前不要换行

StringBuffer sb = new StringBuffer();

// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行

sb.append("zi").append("xin")...   

.append("huang")... 

.append("huang")... 

.append("huang");

方法参数在定义和传入时,多个参数逗号后边必须加空格

method("a", "b", "c");

表达异常的分支时,少用 if-else 方式

if (condition) {              

        ***

        return obj;   

}

// 接着写 else 的业务逻辑代码

如果非得使用 if()...else if()...else...方式表达逻辑,【强制】避免后续代码维护困难,请勿超过 3 层

最多只能是if-elseif-else,不能再多加else if了

所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。 说明:对子类的实现要求,或者调用注意事项,请一并说明

代码需要加注释

所有的类都必须添加创建者和创建日期

创建一个类之后,需要写这些信息

方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值

可以return null

好的单元测试必须遵守 AIR 原则

 

A:Automatic(自动化)

I:Independent(独立性)

R:Repeatable(可重复)

保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序

反例:method2 需要依赖 method1 的执行,将执行结果作为 method2 的输入

单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下

建个package专门用来做测试

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

二、代码互评

我与完成计算频率模块的同学(17060)进行互评

列出他的代码(在代码后面加注释表示我的评价)

package Test;

import java.util.ArrayList;

public class WordFrequency {
    public static String[][] WordFrequency(ArrayList<String> list){ // 方法名命名错误,第一个单词要小写,为wordFrequency    
        String res[] = new String[1000]; // 数组定义错误,为String[] res
        int num[] = new int[1000]; // 数组定义错误,为int[] num
        int i,j; // 缺少空格,为int i, j;
        int sum = 0;
        boolean state;
        for(i=0;i<list.size();i++) { // 缺少空格,为for(i = 0; i < list.size(); i++) {
            state = false;
            if(i==0) { // 缺少空格,为if(i == 0) {
                res[sum] = list.get(0);
                num[0]++;
                sum++;
            }
            else { // else应该写在上面一行
                for(j=0;j<sum;j++) { // 缺少空格,为for(j = 0; j < sum; j++) {
                    if(res[j].equals(list.get(i))) { 
                        num[j]++;
                        state = true;
                        break;
                    }
                }
                if(state == false) {
                    res[sum] = list.get(i);
                    num[sum]++;
                    sum++;
                }
            }
        }
        String[][] str = new String[sum][2];
        for(i=0;i<sum;i++) { // 缺少空格,为for(i = 0; i < sum; i++) {
            str[i][0] = res[i];
            str[i][1] = String.valueOf(num[i]);
        }
        return str;
    }
    
    public static void main(String args[]) {
        ArrayList<String> list =  new ArrayList<String>();
        String res[][];
        list.add("aaa");
        list.add("bbb");
        list.add("cccc");
        list.add("cccc");
        res = WordFrequency(list);
        for(int i=0;i<res.length;i++) { // 缺少空格,为for(int i = 0; i < res.length; i++) {
            for(int j=0;j<res[i].length;j++) { // 缺少空格,为for(int j = 0; j < res[i].length; j++) {
                System.out.print(res[i][j]+" ");
            }
            System.out.println();
        }
    }
}

// 补充说明
// 本段代码缺少必要的注释,创建类时应写上创建者的名字与日期,并且需要对方法进行介绍,简单讲解自己的思路,以及返回了什么类型的值

三、评审总结

  代码不统一规范,他人阅读起来会非常困难,加大工作量。尽管模块简单,但必要的注释必须要有,因为别人可能与你get不到一个点上,将思路说出来,便于他人理解,也容易发现潜在的错误。

四、静态检测:PMD的使用

eclipse自带有下载PMD的方法,我会在参考文献中给出链接。

检测结果(对我自己的模块ListSort进行检测)

红色表示最严重的等级。自己代码存在问题,现在来解决,双击得到更详细的信息(注意第6行有一个红色错误)

右键红色那一栏,显示详细信息,得到

看描述那一栏,显示方法名必须以一个小写字母开头,并且不能是下划线,我们找到自己代码的位置,发现

果然方法名开头是大写的,将其改正并重新check code with PMD,得

第6行的红色错误已经消失

五、小组代码评审

  因为小组代码较长,不直接列出来。主要存在的问题如下:方法名的命名不规范,空格的使用不合理,部分代码换行使用得不合理。

  改进:按照标准规范进行改进。

高级任务

一、测试数据集的设计思路

将程序组装好后,给输入文档里面加入大量的字符,因为功能基本上都确认完毕,所以主要针对性能进行测试。

二、优化前的程序性能指标

展示一下输入文档

后面还有内容,接着利用自带的时间函数进行测试。

关键代码(里面有时间函数)

public class Main {
    public static void main(String args[]){
        long start = System.currentTimeMillis();
        ArrayList<String> inFile = new ArrayList<String>();
        inFile = wcInput.Input(args[0]);
        String[][] outFile = null;
        outFile = WordFrequency.wordFrequency(inFile);
        outFile = ListSort.ListSort(outFile);        
        File f = new File("outFile.txt");
        Output.Output(outFile,f);
        long end = System.currentTimeMillis();
        System.out.println((end - start)+"ms");
    }
}

得到结果为

输出文件为

三、同行评审

(1)不召开预备会议

(2)准备召开评审会议

作者:17054,17045,17059,17060

记录员:17045

讲解员:17059,17060

评审员:17045,17054,17059,17060

主持人:17054

(3)召开评审会议

确定评审目的:提高程序效率:使运行时间减少、内存使用减少,改善代码规范性,确保功能正确性

讲解员讲解程序的需求

每位作者讲解自己的代码和思路

作者讲解时,剩下的组员进行评审并达成共识,发现的缺陷如下:

一:函数头没有该函数功能、参数类型、返回值类型的注释

二:注释使用中文可能导致编码错误

三:类的方法名首字母不能大写

四:条件或循环语句内部只有一条语句时,也需要用花括号

五:部分变量命名不规范,导致语义不清楚,比如File f,应该用File fileName

六:相关变量必须用下划线表示,比如s_1,s_2,不能写为s1,s2

七:不同变量的定义不能在同一行完成

八:性能:插入排序速度较慢

九:性能:定义数组的时候一次性开固定数字的空间,有可能导致内存浪费,更严重可能因为开设不够,导致程序出错,比如:

  计算频率这个模块在使用时,开头用了,这是不合理的

十:功能:功能全部实现且正确

(4)结论 

  此次评审代码运行正常,预期需求实现良好,但是在代码规范性上有所欠缺,给评审过程带来了一些麻烦,有待改进。

  在功能方面需要改进排序算法和解决数组空间问题。

(5)不足与困难

  缺少评审经验,在评审过程中浪费了一些时间。

(6)记录员记录

(7)主持人确定结束

四、优化的设计思路

不创建固定空间的数组,将其改为

算法改为快速排序,关键代码展示

   public static void quickSort(String[][] list, int low, int high) {
        if(low >= high) {
            return;
        }
        int first = low;
        int last = high;
        int key = Integer.parseInt(list[first][1]);
        String key_2 = list[first][0];
        String key_3 = list[first][1];
        String temp;
        
        while(first < last) {
            while(first < last && Integer.parseInt(list[last][1]) < key) {
                --last;
            }
            while(first < last && key_2.compareTo(list[last][0]) != 1 && Integer.parseInt(list[last][1]) == key) {
                --last;
            }
            temp = list[first][0];
            list[first][0] = list[last][0];
            list[last][0] = temp;
            temp = list[first][1];
            list[first][1] = list[last][1];
            list[last][1] = temp;
            
            while(first < last && Integer.parseInt(list[first][1]) > key) {
                first++;
            }
            while(first < last && key_2.compareTo(list[first][0]) == 1 && Integer.parseInt(list[first][1]) == key) {
                first++;
            }
            temp = list[last][0];
            list[last][0] = list[first][0];
            list[first][0] = temp;
            temp = list[last][1];
            list[last][1] = list[first][1];
            list[first][1] = temp;
        }
        list[first][0] = key_2;
        list[first][1] = key_3;
        quickSort(list, low, first - 1);
        quickSort(list, first + 1, high);
    }

得到的输出结果为

程序性能得到提高

五、总结

  通过此次学习,我收获了许多知识。在软件开发的过程中必须要有软件测试相辅助,才能保证软件的质量。在软件开发过程中,团队协作是非常重要的,一个人完成的东西有限,也没有精力照顾到方方面面。并且需要合理的协作才能达到事半功倍的效果。回顾整个过程,一开始根本不会分工,后面一起讨论,确定模块以及模块的功能,然后每个人只需要完成自己的部分,感觉比平时写代码更加高效,也更加体会到了团队的力量。后面一起组装代码,提出改进,使程序更加完善,也使我增加了一份宝贵的经验。

附加题

解题思路

  读到"-x"时,调出文件选择器,通过它来选择输入的文件,如果读到文件名,就通过文件名来选取

关键代码

public class Main extends JFrame implements ActionListener{  
    
    private static final long serialVersionUID = 1L;  
  
    public static File filePath = null;
    public static String Path = null;
    JButton btn = null;  
    JButton choose =null;
  
    JTextField textField = null;  
  
    public Main(){ // 调用文件选择器
        this.setTitle("选择文件窗口");  
        FlowLayout layout = new FlowLayout();// 布局  
        JLabel label = new JLabel("请选择文件:");// 标签  
        textField = new JTextField(30);// 文本域  
        btn = new JButton("浏览");// 钮1 
        choose = new JButton("选择");
  
        // 设置布局  
        layout.setAlignment(FlowLayout.LEFT);// 左对齐  
        this.setLayout(layout);  
        this.setBounds(400, 200, 600, 70);  
        this.setVisible(true);  
        this.setResizable(false);  
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
        btn.addActionListener(this);  
        choose.addActionListener(this); 
        this.add(label);  
        this.add(textField);  
        this.add(btn);  
        this.add(choose);

    }  
  
    public static void main(String[] args){
        if(args.length == 0){
            System.out.println("无输入");
        }
        else if(args.length != 1){
            System.out.println("just one file name");
        }
        else if(args.length == 1){
            if(args[0].equals("-x")) { // 进行判断,是利用文件选择器还是利用文件名
                new Main();
                return;
            }
            String filetype = null;
            filetype = args[0].substring(args[0].length()-3,args[0].length());
            if(!filetype.equals("txt")){
                System.out.printf("must be txt type file");
            }
            else{
                ArrayList<String> inFile = new ArrayList<String>();
                inFile = Input.Input(args[0]);
                String[][] outFile = null;
                outFile = WordFrequency.wordFrequency(inFile);
                ListSort.quickSort(outFile, 0, outFile.length - 1);
                File f = new File("outFile.txt");
                Output.Output(outFile,f);
            }
        }
    }  
  
    @Override  
    public void actionPerformed(ActionEvent e){ // 调用文件选择器后,选择文件并执行
        if(e.getSource() == btn){
            JFileChooser chooser = new JFileChooser();  
            chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);  
            chooser.showDialog(new JLabel(), "选择");  
            filePath = chooser.getSelectedFile(); 
            textField.setText(filePath.getAbsoluteFile().toString());
        }
        if(e.getSource() == choose){
            Path = textField.getText();
            ArrayList<String> inFile = new ArrayList<String>();
            inFile = Input.Input(Path);
            String[][] outFile = null;
            outFile = WordFrequency.wordFrequency(inFile);
            ListSort.quickSort(outFile, 0, outFile.length - 1);
            File f = new File("outFile.txt");
            Output.Output(outFile,f);
        }
    }  
}  

运行结果

输出文件结果

参考文献

黑盒测试:https://www.cnblogs.com/vmorgen/p/6862056.html

白盒测试:https://baike.baidu.com/item/%E7%99%BD%E7%9B%92%E6%B5%8B%E8%AF%95/934440?fr=aladdin

Junit:https://blog.csdn.net/yqj2065/article/details/39967065

PMD:https://www.cnblogs.com/xuehanyu/p/4520730.html

以上是关于软件测试第四周作业 WordCount优化的主要内容,如果未能解决你的问题,请参考以下文章

第四周小组作业:WordCount优化

软测第四周作业 wordcount 优化

wordcount第四周小组作业

第四周WordCount小组作业

第4周小组作业:WordCount优化

第4周小组作业:WordCount优化