WordCount
wordcount
Github地址
https://github.com/MasonWater/WCProject
PSP2.1表格
PSP2.1 | PSP****阶段 | 预估耗时****(分钟) | 实际耗时****(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 832 | 1090 |
· Analysis | · 需求分析 (包括学习新技术) | 75 | 90 |
· Design Spec | · 生成设计文档 | 30 | 45 |
· Design Review | · 设计复审 (和同事审核设计文档) | 15 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 45 | 60 |
· Coding | · 具体编码 | 540 | 720 |
· Code Review | · 代码复审 | 72 | 100 |
· Test | · 测试(自我测试,修改代码,提交修改) | 45 | 55 |
Reporting | 报告 | 85 | 120 |
· Test Report | · 测试报告 | 50 | 70 |
· Size Measurement | · 计算工作量 | 15 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 937 | 1225 |
解题思路
这是个命令行执行的exe程序,所以撇开如何将程序打包成exe不管,首先要做的就是如何正确运行。程序有多个参数,如何获取这些参数?[1]参考资料后了解到需要向main函数传参数。main函数存在数组中,通过循环遍历数组获取传入的参数再将功能参数和文件参数分别存放,用于之后判断功能参数后实现基本功能(统计字符数、单词数、行数)以及扩展功能(统计空行、代码行、注释行以及使用停用词表以及指定输出文件)。
程序设计实现过程
程序包括一个类(wordcount),9个函数,其中包括一个main函数。其余8个函数为:
Operator函数(实现功能)、BaseCount函数(基本功能)、Display函数(设置输出到文件的字符串)、MoreCount函数(扩展功能)、getStopLisy函数(获取停词表中停词)、outputTxt函数(设置输出文件)、Bianli(实现递归处理)、getAllFile函数(获得符合要求后缀的文件名)。
main函数获取到参数后分类,将功能参数和文件参数分别存储之后用于生成wordcount类的一个对象。之后调用对象的Operator功能,循环遍历功能数组判断需要执行那些操作。根据参数判断来调用对应的函数。如事先判断是否有启用停词表的参数,若有则先调用getStopList函数获取停用词表,之后若参数是基本功能的参数,就调用BaseCount函数来统计,同时判断是否去除停词。若是扩展功能参数,如复杂行的统计则调用MoreCount函数来统计代码行,空行和注释行。若是递归调用功能参数则调用BianLi函数,BianLi函数调用getAllfile函数获取符合要求的文件队列然后递归执行相应的操作(BaseCount、MoreCount等)。在递归操作或者Operator执行过程中若有输出文件指定则将输出flag调整。最后调用Display函数设置输出到文件中的内容,之后调用outputTxt函数来根据输出flag输出到文件中。当完成基本功能后,将源文件上传到github仓库中,commit一次,之后完成程序再次上传,打包好exe后完成最后的上传。后续还上传了测试脚本和测试相关文件。
代码说明
BaseCount方法中去除停词时将文件按行读入[2],通过逗号和空格分割单词后和停用词数组中词比较,统计去除词的个数,每行处理一遍。同时注意行开头空格的去除[3]
代码如下:
BufferedReader st2 = new BufferedReader(new FileReader(this.sFilename));//统计单词数
String read1 = null;
int lengthofSplit=0;
while (( read1 =st2.readLine()) != null)
{
read1=read1.trim();//去除空格
String[] arrSplit =read1.split(" |,");//以空格或者逗号分隔
lengthofSplit=arrSplit.length;
for(String s:arrSplit)
{
if(s.length()==0)
lengthofSplit--;//去除空格
}
wordcount+=lengthofSplit;
if(openStopList==1)
{
for(String s:arrSplit)
{
for(String stop:StopList)
if(s.equals(stop))
stopWordCount++;
}
}
}
st2.close();
MoreCount方法中同样也是对文件按行读取,判断多行注释的时候和单行注释一样要判断注释前是否多于一个字符,这通过一个计数变量来实现,以及“*/”后有字符则不算注释行,这也通过计数变量来实现。同时当有“/*”开始后,设置多行注释的flag变量,当遇到“/*”时重置flag
代码如下:
flagOfword=0;
countOfword=0;
numberOfMLines=0;
BufferedReader brm = new BufferedReader(new FileReader(this.sFilename));
String linem;
int flagOfM=0;//标记是否有/*开始了
int countss=0;//记录每行字符数以便判断是否是注释行
int counts=0;//记录每行字符数以便判断是否是注释行
while ((linem = brm.readLine()) != null)//判断/**/类型的注释行
{
String [] sss=new String[10000];
if(flagOfM==1)
numberOfMLines++;
for (int x = 0; x < linem.length(); x++)
{
sss[x]=String.valueOf(linem.charAt(x));
int temp=(x+1)<(linem.length()-1)? x+1:(linem.length()-1);
if(flagOfM==0)
{ countss=0;//
if(x<linem.length()-1&&sss[x].equals("/")&&linem.charAt(temp)=='*')
{
for(int i=0;i<x;i++)
{if(linem.charAt(i)!=' ')
{ if(linem.charAt(i)=='\t')
continue;
countss++;
}
}
if(countss>1)
{
flagOfM=0;
}
else
{
flagOfM=1;
}
continue;
}
}
if(flagOfM==1)
{counts=0;
if(x<linem.length()-1&&sss[x].equals("*")&&linem.charAt(temp)=='/')
{
for(int i=temp+1;i<linem.length()-1;i++)
{if(linem.charAt(i)!=' ')
{ if(linem.charAt(i)=='\t')
continue;
counts++;
}
}
if(counts<=1)
{
numberOfMLines++;
flagOfM=0;
}
else
{
flagOfM=0;
}
}
}
}
}
brm.close();
对于扩展功能里的递归读取符合要求的文件,这通过两个函数实现,BianLi函数调用getAllFile函数来筛选符合要求的文件之后的操作便是循环执行Operator函数的操作,所以BianLi函数不再赘述。关于getAllFile函数,函数有四个参数:文件路径、符合要求的文件/文件夹的存储数组、要筛选的类型以及筛选出来的文件数组。函数通过判断是否是文件夹来操作,若是文件夹则递归调用函数,否则判断类型后删选文件。
代码如下:
private static List<String> getAllFile(File filePath,List<String> filePaths,String type,List<String> finalFiles){
File[] files = filePath.listFiles();//递归获取文件夹以及子目录下的所有符合条件的文件
if(files == null){
return filePaths;
}
for(File f:files){
if(f.isDirectory()){
filePaths.add(f.getPath());
getAllFile(f,filePaths,type,finalFiles);
}else{
if(f.getName().endsWith(type))
{filePaths.add(f.getPath());
finalFiles.add(f.getPath());}
}
}
/*for(String path:finalFiles){
System.out.println(path);
}*/
return finalFiles;
}
测试设计过程
在线学习2.3节对判定的测试中指出“语句覆盖就是要保证设计的测试用例应至少覆盖函数中所有的可执行语句”,为此,我针对程序,使用语句覆盖指标设计测试用例,共产生10个测试用例。
测试中风险高的地方在单词数的统计(是否去除停词),注释行的统计,递归处理文件,因为这这些地方的代码较为复杂。还有Operator函数和递归处理中的类似模块,这些地方判断分支较多,容易出错。
测试用例设计时,我先尽量满足语句覆盖,将各个可执行语句都覆盖到,再考虑到条件覆盖,对可能出现的条件判断的分支进行测试。
设计10个测试用例。
编写的测试脚本"test.bat"内容如下:
wc.exe -c 1111.c
wc.exe -l 1111.c
wc.exe -w 1111.c -e stoplist.txt
wc.exe -a 1111.c
wc.exe -l -a -w 1111.c
wc.exe -l -a -c 1111.c -o output.txt
wc.exe -l -c -w 1111.c -o output.txt
wc.exe -s -c -w *.c
wc.exe -s -c -w d:/TESTC/*.c
wc.exe -s -a -c -w *.c -e stoplist.txt -o output.txt
从上到下的设计为:
测试基本功能统计字符;测试基本功能统计行数;测试基本功能统计单词数并采用停用词表;测试扩展功能空行、代码行以及注释行;测试基本功能和扩展功能一同运行;测试基本功能扩展功能的复杂行和输出文件;测试基本功能和输出文件;测试基本功能和扩展功能的递归处理(只有文件名);测试扩展功能的递归处理(文件给出全路径)以及基本功能;测试扩展功能和基本功能。
文件“1111.c”内容如下:
2222,= Q $4 4
wwef
hel
//
/fgf/
{//
/*HH
HH*/
}/*
hello
这些测试用例基本能符合需求 。
参考文献
[1]java main函数参数传递
https://zhidao.baidu.com/question/568711738.htmlJava
[2]关于按行读取文件
https://www.cnblogs.com/xyzq/p/7095011.html
[3]去除每行开头空格
http://blog.csdn.net/zhangzehai2234/article/details/73522041
[4]Java获取文件后缀名
https://bbs.csdn.net/topics/340104622
[5]java 递归找出文件夹下文件
http://www.cnblogs.com/wangtianze/p/6690644.html