软件质量与测试--第二周作业 WordCount

时间:2022-06-13 19:07:25

软件质量与测试--第二周作业 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+指定的文件名,而不使用一般通配符的情况,会查找目录下所有指定名称的文件。)

 

软件质量与测试--第二周作业 WordCount

注意到需求中有这么一条:是不是可以理解成,如果  不使用-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:

软件质量与测试--第二周作业 WordCount

readFile:读取文件,计算word char line数目。

readFileByLine:读取文件,计算代码行,空行,注释行数目。

writeFile:写文件。将记录结果的内容写入指定文件。

getFilePath:将当前目录下所有文件进行遍历。将符合条件的文件放入ArrayList中。

readIgnoreFile:读取忽视词文件。对word进行相应处理。

 

Main类:

这个控制流程我觉得用图表来表达会好一些。

如下:

软件质量与测试--第二周作业 WordCount

 

 其中有一些小的判断没有进行说明。比如根据文件的格式分辨是绝对路径还是当前路径,还是含有一般通配符的形式等等。

不过大致上我的想法是这样。(觉得不够简练,里面的重复工作也很多。唉.....还要继续练习编程!!)

 

代码说明:

这一部分只会列举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中进行处理。

 

 
  
软件质量与测试--第二周作业 WordCount软件质量与测试--第二周作业 WordCount
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) {

            }
        }

    }
View Code

 

 
  

  

 
 

 

这个是读取文件,计算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. 测试代码行,空行,注释行所用的代码:

软件质量与测试--第二周作业 WordCount软件质量与测试--第二周作业 WordCount
}//
codeline*/
    //noteline
/*nodeine
/*nodeline
*/
/*nodeline*/
//nodeline

*/nodeline
}//nodeline

}/*nodeline*/
}}/*nodeline*/
}

123
View Code

 

 2.测试字符数,单词数,行数,忽视词功能所用代码就借用了老师在毕博平台上的代码。

就不贴了。

 

 对于高风险的地方,我的思考在上述测试用例中有所体现。大致上是对于我认为风险较高的地方进行测试的。

注:此部分测试用例具体代码请查看test.java

 

关于测试脚本:

引用百度百科:

Testing script(测试脚本),一般指的是一个特定测试的一系列指令,这些指令可以被自动化测试工具执行。 为了提高测试脚本的可维护性和可复用性,必须在执行测试脚本之前对它们进行构建。或许会发现这样的情况,即有的操作将出现在几个测试过程中。因此,应有目的地确定这些操作的目标,这样就可以复用它们的实施。 测试脚本是自动执行测试过程(或部分测试过程)的计算机可读指令。测试脚本可以被创建(记录)或使用测试自动化工具自动生成,或用编程语言编程来完成,也可综合前三种方法来完成。

 

以前没有接触过测试脚本。所以也不知道自己编写的算不算测试脚本。

总之我的理解是能够自动化执行程序,得出结果的代码。

那对于这个程序,测试脚本的话,如果把预先设定好的测试用例放进arg数组是不是就可以自动执行,就算是测试脚本?

附上:

软件质量与测试--第二周作业 WordCount软件质量与测试--第二周作业 WordCount
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(" ");
    }//(测试全部功能)

}
View Code

 

 

测试评价:

对于上述的测试用例,我认为大致上是可以覆盖所有功能的路径的。

但可能对于错误的判断的覆盖不高。

也不排除由于我没有思考到而导致的漏洞。

 

 

程序打包记录:

我是使用exe4j来进行打包的。

主要是根据教程来的。(参见参考文献链接)

但在打包的时候遇到一个问题。我觉得有必要记录下来。

 

问题挺简单,就是关于JDK和JRE的版本的问题。因为compile的版本太高,JRE的版本低于就会在生成的时候报错。

后来我在Intellij中调整了一下版本,就成功的生成了exe文件。

 

顺便再提一下exe4j打包过程中的一些设置。

1. 控制台程序请选择该项

软件质量与测试--第二周作业 WordCount

 

2. 添加jar包和寻找主函数

软件质量与测试--第二周作业 WordCount

 

3. 注意一下版本:

软件质量与测试--第二周作业 WordCount

4. 选中左侧search sequence 添加JRE环境

软件质量与测试--第二周作业 WordCount

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分!

 

怎么说呢。今天这两节课......有点没有意义,在上课时间进行这样的争论不是一件很好的事,也不是很理智。

 

其实老师也是很负责。

不知道别的同学想法是什么,但我推测....可能是害怕每周都有一个类似工作量的任务(我指的是...基本每天课余时间都要用来完成,才能做完的任务)

这样的话确实事情很多。也不是很现实。

(而且这么多的任务学院还没有分配上机的课时!!我认为学院分配上机是对同学的一种提醒,提醒同学们有需要编程的任务,并划分出一定的时间专心编程。)

而且人如果被打乱了原有的计划就会很....暴躁。

但对于我来说,既然留了就去做。

大概就这样。