软件测试第四周作业 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优化的主要内容,如果未能解决你的问题,请参考以下文章