软件质量与测试--第二周作业 WordCount
Github地址:
https://github.com/RicardoDZX/WordCount
PSP:
PSP2.1 | PSP 阶段 | 预估耗时 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 300 | 360 |
· Analysis | · 需求分析 (包括学习新技术) | 20 | 40 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 40 |
· Coding | · 具体编码 | 200 | 220 |
· Code Review | · 代码复审 | 10 | 10 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 40 |
Reporting | 报告 | 100 | 100 |
· Test Report | · 测试报告 | 10 | 10 |
· Size Measurement | · 计算工作量 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 80 | 85 |
合计 | 420 | 475 |
解题思路:
首先分析需求:
虽然作业文档中描述不限语言,但根据上课所说,感觉还是使用Java会适合后续的作业?
(好久不用Java....唉....)
首先:
基本功能,就是-w -c -l,分别计算目标文件中的单词,字符,行数。
扩展功能要求递归目录下所有文件,输出更具体信息,获取忽视词。
由于需求好像是在命令行执行,想起来arg[]会保存输入的参数。
(最开始的时候没有留意这一点,就直接读取控制台输入进行分析)
不过后续改了过来。
想法是使用一个ArrayList保存输入的参数和文件。(将arg中元素add到之中)
之后先进行查找 -o命令,-e命令,在当前查找结果的下一个元素认为是文件名。(根据规则)
之后将上述命令和文件名remove掉(主要是为了方便后续的识别)
(需求中好像没有提,-o,-e命令可不可以放在命令的前端,为了应对这种情况)
之后还要处理文件名的问题:有可能是当前路径,有可能是输入绝对路径。
这个我是通过获取文件名,并判断是否含 “\” 即File.separator来进行区分的。
(有点取巧)
在-s递归目录下所有文件的情况下,
如果含有 * 这个通配符,则会选择所有符合条件的文件。(比如*.java,所有后缀名为java的文件)
(-s+指定的文件名,而不使用一般通配符的情况,会查找目录下所有指定名称的文件。)
注意到需求中有这么一条:是不是可以理解成,如果 不使用-o命令的情况下,也会自动进行保存,保存在默认的result.txt文件中。
而如果使用-o 命令,则后面一定跟一个自定义的文件名,将结果保存到该文件中。
对于遍历所有目录下文件的功能,也很简单。
大致思想是获取当前路径,使用file.exist,file.list,file.isdictionary判断。
list获取文件名列表,逐个进行判断是否是目录,还是具体文件。
使用递归很容易进行遍历。
对于忽视词功能,同样是读取指定文件,使用split函数+正则表达式,来进行分割,存入数组。在计算单词的时候进行判断。
(在-w功能的时候也用到了split函数+正则表达式。来分割空格和逗号区分的单词。)
对于代码行/注释行/空行等计算,我的想法是获取文件中的每一行:
如果含有多于一个字符(不包括注释符号),则是代码行。使用trim函数去除空格,或者只含{,},则是空行。
注释行的判断我想的比较复杂。可以参加代码中的注释。
(其实我觉得可以用总行数减去其中两者来获取第三者的数值)
以上,基本功能的实现想法我大致都列举了一下。
目前遇到的一个问题就是关于字符的计数。
我使用毕博上的用例,结果是38,正确结果是34。
问题我大概知道,好像是因为计算的时候把制表符\t,当作三个空格来计算了。
这就很头疼。我不知道为什么会这样。
让我再想想。
(现在已经解决了。能够得出正确结果。)
在这之中,我主要是查找了关于Java的读取,写文件,一些具体函数的功能与使用,命令行输入arg数组的相关知识。
参考网站:
http://blog.csdn.net/sunling_sz/article/details/30476483 有关Java逐行读取文件
http://www.blogjava.net/baizhihui19870626/articles/372872.html 有关递归遍历目录下所有文件。
http://blog.csdn.net/u013063153/article/details/70237241 Java读取文件的几种方法。
程序设计实现:
关于这一点,其实我觉得自己做的并不好。(自我评价)
由于挺久没有做一些”复杂“需求的任务。所以习惯于只在主函数中做各种功能。
所以代码的耦合性太强。
不多说,结构描述如下:
代码主要包括两个类。WordCount类和主函数main类。
其中WordCount类完成对读取文件,写文件,计数等功能的集成。
main类负责主要的操作流程。获取输入,判断,并根据不同情况调用WordCount类中的不同函数,实现功能
WordCount:
readFile:读取文件,计算word char line数目。
readFileByLine:读取文件,计算代码行,空行,注释行数目。
writeFile:写文件。将记录结果的内容写入指定文件。
getFilePath:将当前目录下所有文件进行遍历。将符合条件的文件放入ArrayList中。
readIgnoreFile:读取忽视词文件。对word进行相应处理。
Main类:
这个控制流程我觉得用图表来表达会好一些。
如下:
其中有一些小的判断没有进行说明。比如根据文件的格式分辨是绝对路径还是当前路径,还是含有一般通配符的形式等等。
不过大致上我的想法是这样。(觉得不够简练,里面的重复工作也很多。唉.....还要继续练习编程!!)
代码说明:
这一部分只会列举WordCount类里面的一部分代码。
自己编的代码可能不够美观。
public void readFileByLine(String fileName) throws IOException{ File file = new File(fileName); int emptyLine=0,codeLine=0,exLine=0; int flag=0; int thisline=0; if(file.exists()) { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); String tempString; while ((tempString = br.readLine()) != null) { //读取的不是空行 if(tempString.trim().startsWith("//") ||(tempString.trim().contains("//") && tempString.substring(0,tempString.trim().lastIndexOf("//")).length()<=1) ||tempString.trim().startsWith("/*") ||tempString.trim().endsWith("*/") ||(tempString.trim().contains("/*") && tempString.substring(0,tempString.trim().lastIndexOf("/*")).length()<=1) ){ //本行是注释行 //情况1://开头的一行 //情况2:非//开头,但只有一个字符 //情况3:/*开头的一行 //情况4:之前几行有对应的/*,且本行以*/结尾 //情况5:非/*开头,但只有一个字符。 if(tempString.trim().contains("/*")){ flag=1; thisline=1; } if(tempString.trim().startsWith("/*")){ flag=1; exLine++; } else if(tempString.trim().contains("//")){ exLine++; } else if(tempString.trim().endsWith("*/")&&flag==1){ if(thisline==0) { flag = 0; exLine++; } else { if( tempString.substring(0,tempString.trim().lastIndexOf("/*")).length()<=1){ exLine++;//如果是本行有/*的情况,要判断/*前的字符数量是否大于1. flag=0; } else { codeLine++;//此处是为了填补进入if却不是注释行的情况 } } } else { codeLine++;//此处是为了填补进入if却不是注释行的情况 } thisline=0; } else if(tempString.trim().isEmpty()||tempString.trim().length()<=1){ emptyLine++; } else { codeLine++; } } br.close(); //初始化计数器,并置值 setLine_Code(codeLine); setLine_Empty(emptyLine); setLine_Ex(exLine); } else { System.out.println("该文件不存在,获取代码行等信息毫无意义,请给出正确文件名"); } }
这是计算代码行,空行,注释行的代码。
啊啊啊计算的方法超级超级笨。但真的懒得想。...........额............
不过经过测试应该能覆盖所有情况(起码毕博平台上的测试用例中的情况可以覆盖。)
public void writeFile(String fileName,String info){ File file = new File(fileName); Writer writer=null; try { if(!file.exists()) { file.createNewFile(); } writer = new OutputStreamWriter(new FileOutputStream(file)); writer.write(info); writer.close(); } catch (Exception e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e1) { } } } }
这个是写入文件的代码。解释一下:Info参数是在main中记录输出信息的字符串。
public void getFilePath(ArrayList<String> allFile,String path ,String form){ File file=new File(path); if(file.isDirectory()){//当前路径是文件夹 String[] filelist = file.list(); for (int i = 0; i < filelist.length; i++) { File readfile = new File( path+ File.separator + filelist[i]); if (!readfile.isDirectory()) { if(form.contains("*")) { //当含有*通配符时。 if (readfile.getName().substring(readfile.getName().lastIndexOf(".")).equals(form.substring(form.lastIndexOf(".")))) { //获取文件后缀名,与form格式的后缀名比较,一致时加入。 allFile.add(path + File.separator + filelist[i]); } } else { if (readfile.getName().equals(form)) { //否则只有文件名完全相同时,才add allFile.add(path + File.separator + filelist[i]); } } } else if (readfile.isDirectory()) { //递归遍历所有文件 getFilePath(allFile,path + File.separator + filelist[i],form); } } } else { allFile.add(path); } }
这个是递归遍历目录下所有文件的代码。最终结果是将所有文件放入allFile的ArrayList中,在main中进行处理。
public void readFile(String fileName) throws IOException{ File file = new File(fileName); Reader reader = null; int numchar=0,numword=0,numline=0; if(file.exists()) { // 一次读一个字符 reader = new InputStreamReader(new FileInputStream(file)); int tempchar; while ((tempchar = reader.read()) != -1) { if((char)tempchar!='\r') { numchar++; } } Scanner scanner2 = new Scanner(new InputStreamReader(new FileInputStream(file))); while (scanner2.hasNext()) { numline++; String str = scanner2.nextLine(); // numchar+=str.length(); str=str.trim(); // System.out.println(str.length()); if(str.length()!=0) { String[] chars = str.split("\\s+|,");//根据空格切割,获取每一个单词 // for (String i : chars) { //System.out.println(i); // } // System.out.println(chars.length); numword += chars.length;// } } reader.close(); if(ignoreWord.size()!=0) { ArrayList<String> allList = new ArrayList<>(); if (file.exists()) { // 一次读一个字符 Scanner scanner = new Scanner(new InputStreamReader(new FileInputStream(file))); String igWord = ""; while (scanner.hasNext()) { String str = scanner.nextLine(); String[] chars = str.split("\\s+|,");//根据空格切割,获取每一个字符 //注意这个地方。我觉得很实用,就是通过正则表达式进行分割。多个分割条件。 for (String eachChar : chars) { if (!eachChar.trim().isEmpty()) { allList.add(eachChar); } } } scanner.close(); for (String eachWord : allList) { for (String eachIgnore : ignoreWord) { if (eachWord.equals(eachIgnore)) { // System.out.println(eachIgnore); numword--; } } } } //初始化计数器,并置值 else { System.out.println("该文件不存在,获取忽视字符等信息毫无意义,请给出正确文件名"); } } setChar_num(numchar); setLine_num(numline); setWord_num(numword); } else { System.out.println("该文件不存在,获取字符数等信息毫无意义,请给出正确文件名"); } if (reader != null) { try { reader.close(); } catch (IOException e1) { } } }
这个是读取文件,计算word char line的函数。
大致上主要的想贴上来的代码就这些。
测试设计过程:
设计测试用例。
上面我特意用VISIO画了一个流程图就是为了这个设计测试用例。
(目前只学过白盒测试)
希望流程图能更清晰的表示程序的路径,分支。
这样的话设计测试用例,要尽量遍历所有可能的路径(选择)
(我有点纠结。因为不太理解这个测试用例的含义。)
重点的疑惑:
不知道是说命令行的输入算是测试用例。不同的输入会覆盖不同的路径。
但测试用例是不是还与要读取的文件中的内容有关呢?
假如要读取的文件中没有表现出忽视词的存在,那忽视词的功能应该怎么检测呢?(岂不是永远百发百中毫无问题?)
假如要读取的文件复杂度不够,感觉也会存在问题。不能清楚检查出程序缺陷。
但如果文件太复杂,自己要统计出正确答案就太麻烦了。
嗯.....先记录下疑惑,不多说。测试用例如下:
(暂时只讨论命令行的输入部分。)
1. wc.exe -w -c -l test.java
(走无-o,无-e 无-a的路径,文件名用(默认)当前路径)
2. wc.exe -w -c D:\test\test.java
(同上,走无-l的路径,文件名用绝对路径)
3. wc.exe -w -s *.java -o ouput.txt
(同上,走“-s”路径,使用一般通配符,使用“-o”,声明输出文件)
4. wc.exe -w -s D:\test\*.java
(使用绝对路径和一般通配符)
5. wc.exe -c -l -w -a test.java -e ignore.txt
(测试功能-a,-e忽视词。此处我认为应该注意要有-w,单词数的显示。不然测试-e就没有意义了。不知道这个想法对不对。)
6. wc.exe -c -l -s test.java -e ignore.txt
(走-s,-e路径,使用当前路径,不使用一般通配符。这个情况需求中没有明确给出。但我认为应该效果是遍历当前目录下所有具有给定名称的文件。(重名?))
7. wc.exe -l -a -s D:\test\*.java -o output.txt
(-a+ -s路径。使用输出)
8. wc.exe -c -a test.java -o
(这个用例我想测试对于错误输入的反应。)
9. wc.exe -c -s -a test.java -e
(这个用例我想测试对于错误输入的反应。)
10. wc.exe -w -s test.jva -o output.txt
(假如输入文件的名称错误。应该显示错误信息)
11. wc.exe -e ignore.txt -w -s test.java
(测试-e指令在输入文件指令之前的情况)
12. wc.exe -o output.txt -w -l D:\test\test.java
(测试-o指令在输入文件的操作指令之前的情况)
13. wc.exe -w -l D:\test\*.java
(测试,在没有遍历指令-s的情况下,使用一般通配符,会有什么样的结果。)
14. wc.exe -w -l -c -s -a D:\test\*.java -o output.txt -e ignore.txt
(测试全部功能)
补充测试:
15. wc.exe -e ignore.txt (没有输入文件的情况)
16. wc.exe -e ignor.txt (假设该文件不存在)
17 wc.exe -o output.txt -e ignore.txt
对于测试用的文件(输入文件)中的代码:
1. 测试代码行,空行,注释行所用的代码:
}// codeline*/ //noteline /*nodeine /*nodeline */ /*nodeline*/ //nodeline */nodeline }//nodeline }/*nodeline*/ }}/*nodeline*/ } 123
2.测试字符数,单词数,行数,忽视词功能所用代码就借用了老师在毕博平台上的代码。
就不贴了。
对于高风险的地方,我的思考在上述测试用例中有所体现。大致上是对于我认为风险较高的地方进行测试的。
注:此部分测试用例具体代码请查看test.java
关于测试脚本:
引用百度百科:
Testing script(测试脚本),一般指的是一个特定测试的一系列指令,这些指令可以被自动化测试工具执行。 为了提高测试脚本的可维护性和可复用性,必须在执行测试脚本之前对它们进行构建。或许会发现这样的情况,即有的操作将出现在几个测试过程中。因此,应有目的地确定这些操作的目标,这样就可以复用它们的实施。 测试脚本是自动执行测试过程(或部分测试过程)的计算机可读指令。测试脚本可以被创建(记录)或使用测试自动化工具自动生成,或用编程语言编程来完成,也可综合前三种方法来完成。
以前没有接触过测试脚本。所以也不知道自己编写的算不算测试脚本。
总之我的理解是能够自动化执行程序,得出结果的代码。
那对于这个程序,测试脚本的话,如果把预先设定好的测试用例放进arg数组是不是就可以自动执行,就算是测试脚本?
附上:
package wordCount_dzx; import java.io.*; public class test { public static void test1(){ String[] args="wc.exe -w -c -l test.java".split(" "); }//(走无-o,无-e 无-a的路径,文件名用(默认)当前路径) public static void test2(){ String[] args="wc.exe -w -c D:\\test\\test.java".split(" "); }//(同上,走无-l的路径,文件名用绝对路径) public static void test3(){ String[] args="wc.exe -w -s *.java -o ouput.txt".split(" "); }//(同上,走“-s”路径,使用一般通配符,使用“-o”,声明输出文件) public static void test4(){ String[] args="wc.exe -w -s D:\\test\\*.java ".split(" "); }//(使用绝对路径和一般通配符) public static void test5(){ String[] args="wc.exe -c -l -w -a test.java -e ignore.txt".split(" "); }//(测试功能-a,-e忽视词。此处我认为应该注意要有-w,单词数的显示。不然测试-e就没有意义了。不知道这个想法对不对。) public static void test6(){ String[] args="wc.exe -c -l -s test.java -e ignore.txt".split(" "); }//(走-s,-e路径,使用当前路径,不使用一般通配符。这个情况需求中没有明确给出。但我认为应该效果是遍历当前目录下所有具有给定名称的文件。(重名?)) public static void test7(){ String[] args="wc.exe -l -a -s D:\\test\\*.java -o output.txt".split(" "); }//(-a+ -s路径。使用输出) public static void test8(){ String[] args="wc.exe -c -a test.java -o".split(" "); }//(这个用例我想测试对于错误输入的反应。) public static void test9(){ String[] args=" wc.exe -c -s -a test.java -e".split(" "); }//(这个用例我想测试对于错误输入的反应。) public static void test10(){ String[] args="wc.exe -w -l -c -s -a D:\\test\\*.java -o output.txt -e ignore.txt".split(" "); }//(测试全部功能) }
测试评价:
对于上述的测试用例,我认为大致上是可以覆盖所有功能的路径的。
但可能对于错误的判断的覆盖不高。
也不排除由于我没有思考到而导致的漏洞。
程序打包记录:
我是使用exe4j来进行打包的。
主要是根据教程来的。(参见参考文献链接)
但在打包的时候遇到一个问题。我觉得有必要记录下来。
问题挺简单,就是关于JDK和JRE的版本的问题。因为compile的版本太高,JRE的版本低于就会在生成的时候报错。
后来我在Intellij中调整了一下版本,就成功的生成了exe文件。
顺便再提一下exe4j打包过程中的一些设置。
1. 控制台程序请选择该项
2. 添加jar包和寻找主函数
3. 注意一下版本:
4. 选中左侧search sequence 添加JRE环境
5.应该就这样
参考文献链接:
https://www.cnblogs.com/Berryxiong/p/6232373.html 关于Java split的使用
http://blog.csdn.net/sunling_sz/article/details/30476483 有关Java逐行读取文件
http://www.blogjava.net/baizhihui19870626/articles/372872.html 有关递归遍历目录下所有文件。
想说的话(心得体会):
所以说...编程还是要练。还要熟悉编程的规范和思想。
这次完全是想到哪写到哪.....构思的话也进行了,但是构思的不够完整。
所以最后就放飞自我了。
还是熟悉C++多一点....唉...总之还是自己太菜。
消耗的时间稍微有点多。可能也与自己编程不熟练有关。但我觉得....我至少能代表平均水平???...平均偏下?
大概是每天中午和晚上都会抽出时间,边熟悉Java的一些现有的函数,边去继续写一点实现一点功能。
写到现在,基本上完成,但还有一些收尾工作。
但是还是有很多别的事情。上周还有软件文档写作的大作业。疯狂码字能有1W+。
不可能把空余时间都用来写这个........
所以很难受。
总之提升还是有的。对于我这样的菜鸟也算很大。我也很满意这一点。大概就是....付出多少就会有多少收获?
所以说很矛盾一方面觉得很繁重,很耗时间,读需求读的脑袋痛,但是也有收获。
反正怎么怨念都还是得做.....除非放飞自我等着 倒 扣 110分!
怎么说呢。今天这两节课......有点没有意义,在上课时间进行这样的争论不是一件很好的事,也不是很理智。
其实老师也是很负责。
不知道别的同学想法是什么,但我推测....可能是害怕每周都有一个类似工作量的任务(我指的是...基本每天课余时间都要用来完成,才能做完的任务)
这样的话确实事情很多。也不是很现实。
(而且这么多的任务学院还没有分配上机的课时!!我认为学院分配上机是对同学的一种提醒,提醒同学们有需要编程的任务,并划分出一定的时间专心编程。)
而且人如果被打乱了原有的计划就会很....暴躁。
但对于我来说,既然留了就去做。
大概就这样。