软件测试第二周个人作业WordCount
github地址:https://github.com/Xinli666/test
1.作业需求分析
首先看到的是作业的需求,篇幅比较大而且涉及到了一些比较有争议的地方,所以,我在我自己的理解下对需求的一些地方做了改进。
基本功能
wc.exe -c file.c //返回文件 file.c 的字符数 wc.exe -w file.c //返回文件 file.c 的单词总数 wc.exe -l file.c //返回文件 file.c 的总行数 wc.exe -o outputFile.txt //将结果输出到指定文件outputFile.txt
这里基本没有什么疑问的地方,就是在单词的判定加了一些分隔符比如换行,tab,回车。
扩展功能
wc.exe -s //递归处理目录下符合条件的文件 wc.exe -a file.c //返回更复杂的数据(代码行 / 空行 / 注释行) wc.exe -e stopList.txt // 停用词表,统计文件单词总数时,不统计该表中的单词
扩展功能这边主要的疑问在于代码行,空行,注释行的定义。这个问题在后面老师发了一个公告中做了说明,并且给了一个实例,后来我按实例修改了代码,把实例跑通了。但是还有很多实例没考虑到的
有争议的地方,我就按实例做了,没考虑太多。
高级功能没有时间实现啊,主要上礼拜要交文档写作的结课作业,时间不太够。
2.PSP
PSP2.1表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
200 | 200 |
· Estimate |
· 估计这个任务需要多少时间 |
500 | 600 |
Development |
开发 |
400 | 500 |
· Analysis |
· 需求分析 (包括学习新技术) |
100 | 120 |
· Design Spec |
· 生成设计文档 |
30 | 35 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
20 | 20 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 | 10 |
· Design |
· 具体设计 |
400 | 450 |
· Coding |
· 具体编码 |
500 | 600 |
· Code Review |
· 代码复审 |
60 | 100 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 | 150 |
Reporting |
报告 |
120 | 120 |
· Test Report |
· 测试报告 |
30 | 45 |
· Size Measurement |
· 计算工作量 |
20 | 15 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 | 30 |
合计 |
800 | 1000 |
3.具体模块实现
代码部分我是放在一个类里面的,其中分为7个功能模块,每个模块有1-2个函数,还有一个主函数,在主函数中调用相应模块的函数,并且传递相应参数。
3.1 -c实现
思路:首先要运用到的是java的文件读写包,然后通过行读取文件,字符总数就是每一行字符串长度之和。
代码:
public static int CountCharacter(File f){ //返回字符总数 if (!f.exists()) return -1; int result=0; BufferedReader reader=null; try{ reader = new BufferedReader(new FileReader(f)); String str=null; while((str=reader.readLine())!=null) result+=str.length(); //字符总数就是读到每一行的字符串的长度 } catch (FileNotFoundException e0){ e0.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } return result; }
3.2 -w实现
思路:-w这边需要分两种情况,一种是含有-e的,一种是不含的。含有-e的需要根据停用词表来判断是不是有效的单词。我这边用了重写的方法,完美实现了两种情况,第一个函数只有一个参数就是要计算的文件名,第二个函数包含两个参数,除了文件名还有从停用词文件都出的停用词。然后计算单词数量主要是对读到的字符按分隔符进行组合。每一次读到分隔符就把之前读到的字符组合起来(前提是之前读到的不是分隔符),按照这个逻辑就可以写出代码。
代码:
public static int CountWord(File f){ //返回单词总数(不含-s的情况) if(!f.exists()) return -1; int result=0; BufferedReader reader=null; try{ reader=new BufferedReader(new FileReader(f)); int temp,flag=0; while((temp=reader.read())!=-1){ if((char)temp!='\n'&&(char)temp!='\t'&&(char)temp!=','&&(char)temp!=' '&&(char)temp!='\r'){ flag=1; } else if(flag==1&&((char)temp=='\n'||(char)temp=='\t'||(char)temp==','||(char)temp==' '||(char)temp=='\r')){ flag=0; result++; } } result++; } catch (FileNotFoundException e0){ e0.printStackTrace(); }catch (IOException e) { e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } return result; }
public static int CountWord(File f,ArrayList <String> arr){ //重载返回单词总数(含停用词) if(!f.exists()) return -1; int result=0; BufferedReader reader=null; try{ reader=new BufferedReader(new FileReader(f)); int temp,flag=0; String str=""; while((temp=reader.read())!=-1){ if((char)temp!='\n'&&(char)temp!='\t'&&(char)temp!=','&&(char)temp!=' '&&(char)temp!='\r'){ flag=1; str+=(char)temp; } else if(flag==1&&((char)temp=='\n'||(char)temp=='\t'||(char)temp==','||(char)temp==' '||(char)temp=='\r')){ flag=0; if(!arr.contains(str)){ //在这边将与停用词相同的排除 result++; } str=""; } } result++; } catch (FileNotFoundException e0){ e0.printStackTrace(); }catch (IOException e) { e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } return result; }
3.3 -l实现
思路:-l是最简单的了吧,直接读到一行就+1。
代码:
public static int CountLine(File f){ //返回总行数 if (!f.exists()) return -1; int result=0; BufferedReader reader=null; try{ reader = new BufferedReader(new FileReader(f)); while(reader.readLine()!=null) result++; } catch (FileNotFoundException e0){ e0.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } return result; }
3.4 -o实现
思路:-o的话是对文件的写入,主要思路是将需要写入的所有内容拼接成一个字符串,再最后写入指定文件。
代码:
public static void Output(File f,String s){ //输出到指定文件 BufferedWriter writer=null; try{ writer=new BufferedWriter(new FileWriter(f)); writer.write(s); //将内容写入 } catch (FileNotFoundException e0){ e0.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try{ writer.close(); } catch(IOException e){ e.printStackTrace(); } } }
3.5 -s实现
思路:这边需要分两步进行,首先要将例如(D:\test*.txt)的字符串分解为*前面的路径名和*后面的需要匹配的文件后缀名。然后通过一个函数找出路径名下的所有子文件的文件名,再通过另一个函数比较这些文件名是否符合后缀。筛选出需要的文件。
代码:
public static ArrayList<String> traverse(String filepath){ //递归显示路径下所有文件名 File root = new File(filepath); File[] files = root.listFiles(); ArrayList <String> filelist=new ArrayList<String>(); for(File file:files){ if(file.isDirectory()){ //如果是一个文件夹,就继续递归 traverse(file.getAbsolutePath()); } else{ //如果不是一个文件夹,就吧文件名加到数组中。 filelist.add(file.getName()); //System.out.println(file.getName()); } } return filelist; }
public static ArrayList<String> choose(ArrayList<String> filename,String filefeature){ //根据文件特征符号筛选文件 ArrayList <String> filelist=new ArrayList<String>(); for(int i=0;i<filename.size();i++){ if(filename.get(i).contains(filefeature)){ //如果包含特征符号,那么就是需要的文件 filelist.add(filename.get(i)); } } return filelist; }
3.6 -a实现
思路:这个应该是最复杂的一个模块了,主要复杂在情况太多了。。。。。
空行的算法就是简单的将读到的一行字符串去掉空格之后计算长度,长度为0或者是括号就是空行。
其次考虑的是注释行。。。分为好多好多情况。//,/*开头且没有代码的是注释行,不是//,/*,{//,{/*开头,但含有//,/*的是代码行,在/*和*/之间的是注释行,含有*/但不是*/结尾的是代码行,*/结尾的是注释行。。。还有一些特殊情况我就没考虑了,比如说(注释行中间的空行算不算空行)。具体的判断在代码中有注释(当时一边写一边注释的,不然后面回过头来要看晕)
剩下的就是代码行,用了排除法。
这个函数返回的是三个值组成的数组。
代码:
public static int[] CountComplex(File f){ //返回代码行,注释行,空行个数 int[] arr=new int[3]; int emptyline=0,codeline=0,noteline=0; BufferedReader reader = null; try{ reader = new BufferedReader(new FileReader(f)); String str=null; int flag=0; while((str=reader.readLine())!=null){ str=str.trim(); //去掉两边的空格,这样可以直接通过判断str长度判断是不是空行 if(str.length()==0||str.equals("{")) emptyline++; else if(str.startsWith("//")||str.startsWith("}//")) //如果该行是//开头,那么是注释行,排除了先是代码然后是//的情况。 noteline++; else if(str.contains("/*")){ //如果代码行中存在/* 如果是开头出现,判断是不是在该行结束,如果在中间出现,该行是代码行 if(str.startsWith("/*")||str.startsWith("}/*")){ if(str.contains("*/")){ //如果在本行中存在*/,判断是不是结尾 if(str.endsWith("*/")) noteline++; else codeline++; } else{ //本行不存在*/就把flag置为1,然后找下一行,直到一行包含*/ flag=1; noteline++; } } else{ //不是以/*开头但是包含/*,判断是不是该行结束 if(str.contains("*/")){ //如果本行结束,那么就是代码行 codeline++; } else{ //如果本行没结束,flag置1,本行是代码行 codeline++; flag=1; } } } else if(flag==1){ //如果flag是1,判断本行有没有*/ if(str.contains("*/")){ //如果本行包含*/且flag=1那么表示注释结束,判断是不是结尾 if(str.endsWith("*/")){ //如果*/在结尾,是注释行,不在结尾,是代码行 flag=0; noteline++; } else{ flag=0; codeline++; } } else{ noteline++; } } else{ codeline++; } } } catch(FileNotFoundException e0){ e0.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } arr[0]=codeline; arr[1]=emptyline; arr[2]=noteline; return arr; }
3.7 -e实现
思路:-e这边的实现方法就是从给定的文件读出停用词,然后在前面重载的计算单词函数里面将停用词用参数传递进去。都出停用词的方法类似读出单词,就是忽略一些分隔符,将读到的字符组合起来,最后返回一个停用词组成的动态数组。
代码:
public static ArrayList<String> readstopword(File f){ //停用词读取 ArrayList <String> StopWordList=new ArrayList<String>(); BufferedReader reader=null; try{ int temp,flag=0; String str=""; reader=new BufferedReader(new FileReader(f)); while((temp=reader.read())!=-1){ if((char)temp!='\n'&&(char)temp!='\t'&&(char)temp!=','&&(char)temp!=' '&&(char)temp!='\r'){ flag=1; str+=(char)temp; } else if(flag==1&&((char)temp=='\n'||(char)temp=='\t'||(char)temp==','||(char)temp==' '||(char)temp=='\r')){ flag=0; StopWordList.add(str); str=""; } } StopWordList.add(str); } catch (FileNotFoundException e0){ e0.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ try{ reader.close(); } catch(IOException e){ e.printStackTrace(); } } return StopWordList; }
3.8 主函数逻辑
思路:最后就是如何运用这些写好的方法了。首先这个逻辑确实比较复杂,一些-c,-w,-l,-o,-a,-s,-e的组合搞得我很头痛。但是一步一步进行下来后又感觉比较顺畅。具体的实现我是通过了几个标志来判断是否写入了-c,-w,-l,-o,-a,-s,-e,然后通过排列的方式判断输入的是需要计算的文件名还是停用词文件名还是需要输出的文件名,具体的逻辑在代码注释中都有给出。然后在实现到-s的时候发现前面写的东西有一点问题,因为前面只考虑到了单个文件的读取,但是引入-s之后出现了多个文件的读取,又修改了代码,修改了逻辑。之后又引入了-e,又修改了逻辑。。。总之过程很艰辛,不过还算顺利。
代码:
public static void main(String[] args){ boolean is_c=false,is_w=false,is_l=false,is_o=false,is_a=false,is_e=false,is_s=false; File outputfilename=null,stopwordfile=null; String path="",feature="",str=""; ArrayList <String> str1=new ArrayList <String>(); ArrayList <File> filename=new ArrayList<File>(); if(args.length==0){ System.out.println("你没有传入任何参数"); } else{ int i=0; for(i=0;i<args.length;++i){ //遍历args,用标志记录出现的运算 if(args[i].equals("-c")){ is_c=true; } else if(args[i].equals("-w")){ is_w=true; } else if(args[i].equals("-l")){ is_l=true; } else if(args[i].equals("-o")){ is_o=true; } else if(args[i].equals("-a")){ is_a=true; } else if(args[i].equals("-e")){ is_e=true; } else if(args[i].equals("-s")){ is_s=true; //traverse("D://workspace/test"); } else{ //根据在出现文件名之前有没有出现-o判断是输出文件还是读文件 if(is_o){ outputfilename=new File(args[i]); } else if(is_e){ //根据在出现文件名之前有没有出现-e判断是停用词文件还是读文件 stopwordfile=new File(args[i]); // readstopword(stopwordfile); } else if(!is_s){ //不包含-s只需要将该参数当作文件名 filename.add(new File(args[i])); str1.add(args[i]); } else{ //包含-s需要分析目录下符合条件的文件 String [] s=args[i].split("\\*"); //将该参数按*分开,前面是路径,后面是文件的特征符号 path=s[0]; feature=s[1]; for(int j=0;j<choose(traverse(path),feature).size();++j){ filename.add(new File(choose(traverse(path),feature).get(j))); str1.add(choose(traverse(path),feature).get(j)); } } } } } //System.out.println(is_o); for(int i=0;i<filename.size();++i){ //分两种情况,一种是含-s,那么将由多个文件倍计算,不含-s只有单一文件 if(is_c){ System.out.println(str1.get(i)+",字符数: "+CountCharacter(filename.get(i))); } if(is_w&&(!is_e)){ System.out.println(str1.get(i)+",单词数: "+CountWord(filename.get(i))); } else if(is_w&&is_e){ System.out.println(str1.get(i)+",单词数: "+CountWord(filename.get(i),readstopword(stopwordfile))); } if(is_l){ System.out.println(str1.get(i)+",行数: "+CountLine(filename.get(i))); } if(is_a){ System.out.println(str1.get(i)+",代码行/空行/注释行: "+CountComplex(filename.get(i))[0]+"/"+CountComplex(filename.get(i))[1]+"/"+CountComplex(filename.get(i))[2]); } if(is_o){ if(is_c){ str+=str1.get(i)+",字符数: "+CountCharacter(filename.get(i))+"\r\n"; } if(is_w){ str+=str1.get(i)+",单词数: "+CountWord(filename.get(i))+"\r\n"; } if(is_l){ str+=str1.get(i)+",行数: "+CountLine(filename.get(i))+"\r\n"; } if(is_a){ str+=str1.get(i)+",代码行/空行/注释行: "+CountComplex(filename.get(i))[0]+"/"+CountComplex(filename.get(i))[1]+"/"+CountComplex(filename.get(i))[2]+"\r\n"; } //Output(outputfilename,str); } } if(is_o){ Output(outputfilename,str); } //File filename=new File("1.txt"); //System.out.println(CountComplex(filename)[0]+" "+CountComplex(filename)[1]+" "+CountComplex(filename)[2]); //System.out.println(CountCharacter(filename)); //System.out.println(CountWord(filename)); //System.out.println(CountLine(filename)); }
4.测试用例
4.1设计
这个实验需要测试的地方很多,有风险的点也很多。
1.单词的统计。这边单词统计可能出现问题的地方在于在-e出现的情况下需要对停用子进行匹配,在没有出现-e的情况下需要对每一种分隔符进行测试,还有多个分隔符连续出现和文件结尾是否是分割符的情况测试。
2.子文件递归调用。这边对文件的路径和文件的后缀的组合方式有很大风险,因为需要对输入的参数进行分割,在这边*的位置可能影响最后结果。
3.一些字符串的匹配问题。字符串的匹配问题被多次用到,比如说文件后缀的匹配,注释符号//的匹配等等。这些匹配问题都有一个特殊情况就是比如说文件名是c.txt会不会匹配到*.c的情况中。
4.主函数逻辑。这边的问题主要在各种参数的顺序,是否出现。比如-c -w 和-w -c应该是相同结果,-s -c -w -l ....*.txt -e stop.txt -o out.txt 这边的逻辑就十分复杂,首先要提取出输入的是文件名还是操作名,然后还要将文件名分为读取的文件名,路径文件名,输出的文件名和停用词的文件名。操作名又要根据顺序进行输出。
根据这一些风险,我设计了这些用例。
4.2用例和运行结果
这边我用了myeclips的args输入。
测试所读取文件1:(名字:1.txt 这边模仿的是老师给出的用例(为了在计算注释行的时候和老师的相同))
dd
{
ddd
dd
dd
dd
//dddd
dd
/*dd*/
/*ff
*/
/*ff*/
/*ff
//ff
*/ff
ff
ff
}//ff
ff
}/*ff*/
测试所读取文件2:(名字:2.txt。这个主要测试停用词功能)
hot if jf life,while dd switch if hh
测试所读取文件3:(名字:3.txt。这个主要是验证-s下子文件读取是否正常还有对-c,-w,-l的检查)
dddddD sdsd
dwd dddd
ddd dddddd
停用词表(名字:stop.txt)
if switch while
输出文件(名字:output.txt)
测试所用到的路径:D:\workspace\新建文件夹\test
4.2.1用例1
-c -w -l 3.txt -o output.txt 测试-c,-w,-l,-o功能
运行结果 输出文件结果:
4.2.2用例2
-l -w -c 3.txt -o output.txt 与第一个比较,测试主函数逻辑中的-l,-c,-w顺序逻辑是否正确
运行结果 输出文件结果
4.2.3用例3
-w 2.txt 测试2.txt这个专门测试停用词的文件的单词数
运行结果
4.2.4用例4
-w 2.txt -e stop.txt 与第三个比较,看停用词功能是否正常
运行结果
4.2.5用例5
-a 1.txt -o output.txt 测试-a的功能问题,采用了和老师给出例子类似的1.txt
运行结果
4.2.6用例6
-s -c D:\workspace\新建文件夹\test*.txt -o output.txt 测试-s功能是否有问题。
运行结果
4.2.7用例7
-s -c -a D:\workspace\新建文件夹\test*.txt 测试-s下一些功能的组合。
运行结果
1.txt,字符数: 68
1.txt,代码行/空行/注释行: 10/3/9
2.txt,字符数: 55
2.txt,代码行/空行/注释行: 1/0/0
3.txt,字符数: 30
3.txt,代码行/空行/注释行: 3/0/0
output.txt,字符数: 73
output.txt,代码行/空行/注释行: 5/0/0
stop.txt,字符数: 15
stop.txt,代码行/空行/注释行: 1/0/0 截图截不下了。。就复制了结果
4.2.8用例8
-s -w D:\workspace\新建文件夹\test*.txt -e stop.txt 测试-s下面停用词表的功能
运行结果
1.txt,单词数: 20
2.txt,单词数: 5
3.txt,单词数: 6
output.txt,单词数: 16
stop.txt,单词数: 0
4.2.9用例9
-s -a D:\workspace\新建文件夹\test*.txt -o output.txt 测试-s下的-o的功能
输出文件
4.2.10用例10
-s -c -w -l -a D:\workspace\新建文件夹\test*.txt -e stop.txt -o output.txt 通通一起上。。。
运行结果:
1.txt,字符数: 68
1.txt,单词数: 20
1.txt,行数: 22
1.txt,代码行/空行/注释行: 10/3/9
2.txt,字符数: 55
2.txt,单词数: 5
2.txt,行数: 1
2.txt,代码行/空行/注释行: 1/0/0
3.txt,字符数: 30
3.txt,单词数: 6
3.txt,行数: 3
3.txt,代码行/空行/注释行: 3/0/0
output.txt,字符数: 124
output.txt,单词数: 16
output.txt,行数: 5
output.txt,代码行/空行/注释行: 5/0/0
stop.txt,字符数: 15
stop.txt,单词数: 0
stop.txt,行数: 1stop.txt,代码行/空行/注释行: 1/0/0
5.参考文献
http://www.cnblogs.com/ningjing-zhiyuan/p/8563562.html