Hello,WordCountPro
Github地址:
https://github.com/YuQiao0303/WordCountPro
简述
WordCountPro是我们小组开发的具有统计单词与词频功能的程序,编写语言为JAVA,开发平台为Eclipse,完成了基本任务与静态检查。
我们将WordCountPro分为输入检查、核心处理、排序输出三个模块。其中,我与余乔同学一起负责核心处理模块,并分别对该模块进行测试。测试中我应用了白盒测试的独立路径方法与黑盒测试的等价类测试方法。经讨论,我的小组贡献率为:0.26
项目总实施时间略小于预计时间,PSP表格如下:
PSP2.1 | PSP阶段 | 预估耗时 | 实际耗时 |
---|---|---|---|
(分钟) | (分钟) | ||
Planning | 计划 | 15 | 5 |
· Estimate | · 估计这个任务需要多少时间 | 15 | 5 |
Development | 开发 | 1310 | 959 |
· Analysis | · 需求分析 (包括学习新技术) | 360 | 218 |
· Design Spec | · 生成设计文档 | 180 | 31 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 240 | 0 |
· Design | · 具体设计 | 20 | 0 |
· Coding | · 具体编码 | 120 | 130 |
· Code Review | · 代码复审 | 90 | 0 |
· Test | · 测试(自我测试,修改代码,提交修改) | 240 | 580 |
Reporting | 报告 | 360 | 390 |
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 0 | 0 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 360 | 390 |
合计 | 1685 | 1354 |
基本任务
一、模块设计
1、类设计与分工
本次我们将所有功能集成在WordCountPro类中,该类结构如下:
分工如下:
isInputValid() :商莹
wcPro() :雷佳谕(我),余乔。(结对编程)
output():蒋雨晨
我与余乔一起负责核心处理模块wcPro(),该模块主要功能如下:
对用户所输入的合法的待统计文件按需求规定进行单词与词频统计,并将统计结果交给排序输出模块进行输出。
2、设计思路
首先明确待解决问题:
1、接收待统计文件
2、统计
3、保存统计结果并传给下一个模块
对于第一个功能,考虑最终运行时是主函数传参,故文件名是主函数args[]字符串数组中第一个元素args[0]。在输入验证合法后,只需将args[0]作为参数传入wcPro方法即可。
对于第二个功能,结合上一次WordCount作业,我们决定采用一种特殊的数据结构——TreeMap<String,Integer>
,TreeMap 是一个有序的key-value集合,存储的是key-value键值对,基于key(键)进行排序,通过红黑树实现。在这里,我们设置String(单词)为key(键),Integer(词频)为value(值)。之所以设置键值为单词,是因为TreeMap有containsKey(key)方法判别键值是否存在;put(key,value)方法插入新的键值;还有get(key)方法获取key的value,刚好对应了判断单词是否已存在,不存在插入,存在词频增加的功能。故统计功能将应用TreeMap实现,并将最终统计结果保存在TreeMap中。
对于第三个功能,由二知结果已经存入TreeMap,只需要使排序输出模块能够得到该TreeMap即可。故只需要将TreeMap<String,Integer>
设置为WordCountPro类的属性,wcPro设置其值后,排序输出模块直接应用该属性即可。
3、代码展示
static void wcPro(String input) throws IOException{
/*将单词和词频存入info
* 雷佳谕、余乔
* */
File file=new File(input);
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
//读取文件
while((line=br.readLine())!=null){
line=line.toLowerCase();
String reg1 = "[\\s~`!#%\\^&\\*_\\.\\(\\)\\[\\]\\+=:;\"'\\|<>\\,/\\?0-9]+";
String containLetter=".*[a-z].*";
//将读取的文本用规定的常用字符进行分割
String words[] = line.split(reg1);
for(String word: words){
if(word.equals("")||!word.matches(containLetter))
{
continue;
}
int firstIndex=0,lastIndex=word.length()-1;
while(word.charAt(firstIndex)=='-')//寻找当前单词第一个字母的坐标
{
firstIndex++;
}
while(word.charAt(lastIndex)=='-')//寻找当前单词最后一个字母的坐标
{
lastIndex--;
}
//根据游标提取单词
word=word.substring(firstIndex, lastIndex+1);
if(!Info.containsKey(word)){
Info.put(word,1);
} else{
Info.put(word,Info.get(word)+1);
}
}
}
// 关闭文件指针
br.close();
}
二、测试设计
1、黑盒测试
1、测试原理
将整个文件作为输入域,应用等价类测试方法将输入域划分为若干等价类,划分结果如下:
划分思路:
根据需求,所有没有出现单词的文件都为无效等价类;
所有出现单词的文件都为有效等价类;
在此基础上,对无效等价类与有效等价类进行细分。最终使得任何文件输入都符合某一等价类条件。
测试用例设计:
针对每个等价类,分别设置对应的测试用例,共11个。根据重要级别及复杂程度,对一些高重要级别与高复杂度的等价类增设2-3个测试用例,最终测试用例个数为14个。
2、测试用例设计
3、测试用例评价
①测试用例覆盖度:高
等价类划分法的重要标准之一是所有独立等价类的并集是整个输入域。而我所划分的11个等价类可以包括所有输入情况,故测试用例覆盖度高。
②测试用例数量:少
由于程序本身规模不大,又是将整体作为输入域,故包含所有等价类情况的测试用例数量最少只有11个,可以在有限资源下全部进行测试,故测试用例数量少。
③测试用例冗余度:中
对于高重要级别的测试用例,增设了2-3个重复等价类类型的测试用例,故有一定的冗余度,所以测试用例冗余度为中。
④测试用例缺陷定位能力:高
由于所有等价类互相独立,所以在某一等价类的测试用例未通过时,可以针对该等价类描述的代码路径进行检查。对于路径较长的情况,可以分段划分代码,若涉及某一代码段的所有等价类都未通过,则该代码段有问题;若涉及某一代码段的部分等价类通过测试,则该段代码无问题,从而确定哪段代码出现错误。
⑤测试方法复杂度:低
本测试方法的复杂度来自于输入文件的设置,由于每个等价类都对输入有清晰的要求,所以测试方法复杂度低。
2、白盒测试
1、测试原理
本段模块包含循环和多个串联判定结构,所以适合用路径测试方法。选出个数与圈复杂度相同的一组线性无关的路径。根据程序及需求确定每条路径的输入和输出,并涉及相应测试用例。
2、测试用例
程序图如下:
①A->B->D->E->F->G->H->J->L->M->N->E->B->C
涉及判定节点最多,涉及语句较多,执行概率高的基础路径。
②A->B->C
在基础路径B节点上走另一条分支的路径
③A->B->D->E->B->C
在基础路径E节点上走另一条分支的路径
④A->B->D->E->F->E->B->C
在基础路径F节点上走另一条分支的路径
⑤A->B->D->E->F->G->H->I->H->J->L->M->N->E->B->C
在基础路径H节点上走另一条分支的路径
⑥A->B->D->E->F->G->H->J->K->J->L->M->N->E->B->C
在基础路径J节点上走另一条分支的路径
⑦A->B->D->E->F->G->H->J->L->M->O->E->B->C
在基础路径M节点上走另一条分支的路径
不可行路径分析:
①测试用例②属于输入为空文件的情况,此情况在黑盒测试中已经讨论,故舍去;
②测试用例③无法实现,因为若不为空文件,则每一行至少有一个空字符,故无法实现D->E->B,舍去;
③测试用例⑦也无法实现,因为第一个单词不可能已经存在于已有单词集合中,故想走M->O路径,必须构建一个重复出现的单词,故测试用例⑦修改为:
A->B->D->E->F->G->H->J->L->M->N->E->F->G->H->J->L->M->O->E->B->C;
④测试用例④中,F节点的判断条件为”或”条件,故对于F节点取真设置两个输入,使得F节点分别取(0|1)与(1|0);
最终测试用例设计:
3、测试用例评价
①测试用例覆盖度:高
路径测试追求的目标之一是测试的完备性。因为程序的任一执行路径必能够由这些线性无关的路径组合得到,故测试用例覆盖度高。
②测试用例数量:少
环复杂度为7,本应该为7个测试用例;经过路径分析后减少为6个。故测试用例数量少。
③测试用例冗余度:低
所有测试用例线性无关,路径④虽然设计了两个测试用例,但应用了不同的判定条件组合,故测试用例冗余度低。
④测试用例缺陷定位能力:高
白盒测试最大的特点就是测试用例与程序结构密切相关。在路径测试下,每个测试用例都对应特定的路径,每条独立路径最多只有两条分支路径不同。故一旦有测试用例执行失败,可迅速定位出现问题的分支。所以测试用例缺陷定位能力高。
⑤测试方法复杂度:低
只需要针对每条路径设计相应文件输入即可,故测试方法复杂度低。
三、测试运行及评价
1、运行单元测试
本次应用了Eclipse下的Junit4单元测试框架,单元测试运行结果如下:
运行截图:
(1)
第一次出现2个Failure,我先核对了标准统计结果,发现标准统计结果有误,更改后再运行通过:
(2)
单元测试效果:
考虑到每个测试用例的输入输出都不同,但是所测方法都相同,所以我使用了参数化的单元测试方法,在单元测试的内部初始化了所有输入输出。但是由于输入输出较多,在初始化参数时花费了很多时间。
由于这次比较的是两棵TreeMap树,我开始还有些担心assertEquals函数能否正确比较两颗TreeMap,后来结果证明可以正确比较,说明assertEquals函数对于所比较得数据类型考虑得很完备。
第一次得单元测试结果报错,后证实为我自己在手动输入预期输出时出错。订正了预期输出后,单元测试一次通过,说明单元测试本身没有任何错误,参数也都正确传入。
由于这次待测对象只是一个类方法,所以没有用测试集的方法,只应用了参数化测试的方法,但是已经感觉到了Junit测试的强大。相比较以前人为在main函数中手动输入一个个测试用例,Junit单元测试框架效率更高,更加系统和专业。
2、模块质量评价
1、功能性
完备性:
本模块覆盖了用户在需求中提出的所有情况——
第一,由连续的若干个英文字母组成的字符串,例如,software;
第二,用连字符(即短横线)所连接的若干个英文单词视为1个单词,例如,content-based,视为1个单词;
第三,单词不区分大小写;
第四,Let’s,这种包含单引号的情况,视为2个单词,即let和s;
第五,night-,带短横线的单词,视为1个单词,即night;
第六,“I,带双引号的单词,视为1个单词,即i;
第七,TABLE1-2,带数字的单词,视为1个单词,即table。
正确性:
测试用例覆盖了以上所有情况,且在单元测试中所有测试用例均通过,故模块正确性高。
2、可靠性
在模块编写完成后,在本电脑上多次执行测试用例,都通过;在代码合并后,又多次在各小组成员的不同电脑上执行了测试用例,也都通过。故模块可靠性高。
3、易用性
只需输入待测文件路径便可得到统计结果,故易理解,易使用。
扩展任务
一、规范选择及理解
本小组选择了《阿里巴巴Java开发手册(纪念版)》作为这次参照的开发规范
文档链接:https://yq.aliyun.com/attachment/download/?id=4942(需要注册登录)
我从命名规范、常量定义、代码格式以及注释规范四个方面选择了一些规范进行讨论:
(1)命名规范:
①【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO 等。
②【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。
③【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。
理解:以上都是针对命名的规范,统一的命名标准不仅使得代码更整洁,也使得后期维护和使用代码更方便,所以良好的命名风格不失为一种良好的编程习惯。
(2)常量定义
①【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
理解:未经定义的常量对别人来说是不可理解的,会大大增加代码维护和阅读的难度。应该使用枚举、常量、对象等来对 magic number 加上有意义的定义,增加代码可读性。
(3)代码格式
① 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则::(1) 左大括号前不换行。 (2) 左大括号后换行。 (3) 右大括号前换行。 (4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
②【强制】任何二目、三目运算符的左右两边都需要加一个空格。 说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
③ 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
④【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
理解:这些针对代码格式的规范,目的都是在尽可能减少空间的情况下增加代码的可读性。但对于第①条规范中“左大括号前不换行”的规定,我认为有些不合适。因为左大括号前换行更容易标明类、方法、判定条件、循环等的作用范围,更有利于阅读。
(4)注释规范
① 【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 // xxx 方式。
② 【强制】所有的类都必须添加创建者和创建日期
③ 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。
④ 【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的 一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担
理解:注释本身作为增加代码可读性的一部分也存在有格式规范的问题。统一注释格式可以进而统一阅读注释的方法,从而增加程序的可读性与可维护性。
二、交叉评审
本次评审我选择了蒋雨晨(17161)同学编写的较复杂的排序输出模块。代码如下:
从命名规范、常量定义、代码格式以及注释规范四个方面根据之前选择的规范进行评审。
(1)命名规范
方法名与大部分变量都统一使用 lowerCamelCase 风格,遵从驼峰形式。但是第115行声明File类对象writefile变量时没有使用 lowerCamelCase 风格。
(2)常量定义
第121行:int flag = 0;
第126行:if(flag>=100)
都应用了“魔法值”(未经定义便使用的常量)。
(3)代码格式
第126行:If语句后无大括号:
if(flag>=100)
break;
第132行:最后一个catch前的}后没有换行:
}catch(Exception e){
第101、102、106行:注释的双斜线与注释内容之间没有空格;
第115、123、124、126行:双目运算符"="、"+"、">=",左右两边没有空格。
(4)注释规范
第101、102、106行:内部单行注释时,都在被注释语句上方另起一行,使用//注释。符合规范;
缺点是在一些重要的变量和处理前无注释。
三、静态检测
本次应用的静态代码扫描工具为阿里巴巴推出的Java代码检测IDE插件,Eclipse下的下载地址为:https://p3c.alibaba.com/plugin/eclipse/update ,详细安装过程参考附录链接。
阿里巴巴代码检测将静态检测的错误分为三种类型:
Blocker: 即系统无法执行、崩溃或严重资源不足、应用模块无法启动或异常退出、无法测试、造成系统不稳定。
Critical:即影响系统功能或操作,主要功能存在严重缺陷,但不会影响到系统稳定性。
Major:即界面、性能缺陷、兼容性。
我与余乔同学所编写的模块wcPro只有Critical与Major两种类型的错误,报错如下:
1、Critical
根据Oop规约第六条:【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
wcPro模块中word.equals("")违反该规则,应该为“”.equals(word);
2、Major
①
根据报错,本代码中String words[] = line.split(reg1);
不合规范,应改为String[] words = line.split(reg1);
②
根据注释规约第4条:【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。
本代码
while(word.charAt(firstIndex)=='-')//寻找第一个字母的坐标
{
firstIndex++;
}
while(word.charAt(lastIndex)=='-')//寻找最后一个字母的坐标
{
lastIndex--;
}
不合规范,应改为:
// 寻找第一个字母的坐标
while(word.charAt(firstIndex)=='-') {
firstIndex++;
}
// 寻找最后一个字母的坐标
while(word.charAt(lastIndex)=='-') {
lastIndex--;
}
③
根据常量定义第一条:【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
本代码中
word.charAt(firstIndex)=='-'
word.charAt(lastIndex)=='-'
直接使用了字符常量‘-’,不合规定,应改为:
先声明public static final char keyWword = '-';
然后将上述代码订正为:
word.charAt(firstIndex)==keyword;
word.charAt(lastIndex)==keyword;
四、小组代码评价
整体代码单元测试结果如下:
发现大多数报错与违反命名规范、注释规范有关。少部分错误是程序本身存在的可能导致异常的违例。可能是因为我们没有做过需要迭代、移植的大项目,而一直是完成作业,所以我们对注释和命名的规范意识较淡薄。其次,一些细节问题,例如Oop规约第六条"【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals"等,我们也从来没有注意过,但实际上遵守这种规范可以增强代码的准确性和健壮性,是值得重视的。
总体来说,我们的代码需要在命名与注释上进行改进,并对一些可能导致异常的调用及一些不利于代码维护的常量使用上进行更正。
参考资料:
- 【1】Eclipse安装静态检测工具:
https://blog.csdn.net/qq_37552993/article/details/79202267- 【2】 测试用例设计清单:
http://www.51testing.com/html/92/450992-247595.html- 【3】软件质量评估标准:
https://baike.baidu.com/item/软件质量评估/5851341?fr=aladdin- 【9】Bug严重程度分级:
https://blog.csdn.net/fly910905/article/details/78246027?utm_source=debugrun&utm_medium=referral