WordCount by Java

WordCount by Java

软测第二周做业

该项目github地址以下: https://github.com/YuQiao0303/WordCount #1、概述 项目WordCount的需求能够归纳为:对程序设计语言源文件统计字符数、单词数、行数,统计结果以指定格式输出到默认文件中,以及其余扩展功能,并可以快速地处理多个文件。html

具体来讲,需求可参见网址: http://www.cnblogs.com/ningjing-zhiyuan/p/8563562.htmljava

注意,这里认为若是行内有一个非“{”、“}”或“;”且非空格tab等的字符,认为是代码行。git

该项目的PSP表格以下: github

#2、解题思路正则表达式

刚拿到项目时,感到整体不难,可是须要攻克的技术点不少很杂,算法逻辑可能也比较绕。算法

具体来讲,须要重点解决下面几个大问题:express

1.捡起封尘已久的java及MyEclipse基本用法(如何建立项目,String如何使用等)数组

##2.如何读写文件框架

采用了java读写文件1的基本方法,配合以java读写文件2中使用的异常类,轻松完成功能。函数

其中用到了以下几个类:

import java.io.File;  
import java.io.InputStreamReader;
import java.io.BufferedReader;  
import java.io.BufferedWriter;  
import java.io.FileInputStream;  
import java.io.FileWriter; 
import java.io.IOException;

##3.如何递归找到给定文件及其子目录下的全部文件 采用了递归获得全部文件名中的方法。

File类自带两个很好用的方法:

list() 方法:获得路径下全部文件名,不含路径; & listFiles()方法:获得路径下全部文件名,含绝对路径。

经过递归调用这两个方法,便可轻松实现要求。

##4.如何提取文件后缀名 采用了java经过文件名判断文件类型中的方法。直接找到文件名中最后一个“.”的坐标,而后取子串便可。

fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();

查到的这一资料让我了解了java中String类的substring方法,以后常常用到。

##5.如何判断某一行属于代码行/空行仍是注释行 起初,我认为在给出了清晰的断定标准后,用不少断定分支便可达到目的。当我为此方法设计了一个小时后,我感到这样的算法,实在太复杂难懂容易错。

后来听到室友偶尔提了一句“好像判断代码行注释行,能够用正则表达式”。

因而百度正则表达式。在菜鸟教程-java正则表达式中简要学习了正则表达式的相关知识。大致而言,是用于匹配符合必定格式的字符串。

有了这个工具,判断的算法就清晰多了。具体实现请看后文。

##6.如何给主方法传参数,并经过逻辑达到需求

//主方法
	public static void main(String[] args)
	{

		for(int i=0;i<args.length;i++)
		{
			//......
		}
		//......
		return 0;
	}

明白了如何传参,接下来的思路是遍历参数数组,使用switch语句判断每一个参数的具体状况。

具体实现见后文。

##7.如何完成停用词表功能 在室友推荐下,参考了java 统计每一个单词出现的个数

主要用到两个方法: (1) String 类的split(String reg)方法

该方法将正则表达式reg的内容做为分隔符,将字符串按照分隔符分红好几个部分,各部分存放在一个字符串数组中; 方法返回该字符串数组。

因而将正则表达式置为空格,就能够获得输入文件中的单词数组。:

String str[] = line.split(reg1);

(2) TreeMap类的containsKey(String str))方法

该方法返回一个布尔值,若调用它的TreeMap实例中含有str,则返回true,不然返回false。

TreeMap类的功能远不止于此,但此处用到的就是这个“判断是否在集合中”的方法。

只需提早将停用词表进行分割,获得的全部单词放入一个TreeMap对象;再将以前获得的,输入文件的单词数组中的每一单词,都做为参数,判断containsKey的值,便可知道该单词是否要被停用。

8.如何将java程序打包生成exe 直接参考了如何将java程序打包生成exe的方法。

其中,不会像文中那样使用MANIFEST.MF文件,故用了系统默认的MANIFEST.MF文件,同时手动选择主动类的主方法便可。

9.捡起封尘已久的git用法 重学了廖雪峰老师的git教程 经常使用的git指令以下:

$ git add *
$ git status
$ git commit -m "commit message"
$ git push origin master

同时学习了commit message的写法。 #3、程序设计实现过程 因为程序功能不复杂,且参数均是从控制台传给主方法,故只用了一个类。该类类图以下:

下面是各个属性和方法的说明(所有是public static): ###属性说明

String inputFile;		//用户输入的文件路径
	String outputFile;	    //输出信息的文件名
	String stopList;		//停用词表
	
	int chars;			     //字符数
	int words;   			 //单词数
	int lines;			    //行数
	int codeLines;		    //代码行数
	int empLines;			//空行数
	int comLines;			//注释行数

	boolean needC;		//输入参数中是否有“-c”
	boolean needW;		//输入参数中是否有“-w”
	boolean needL;		//输入参数中是否有“-l”
	boolean needO;		//输入参数中是否有“-o”

	boolean needS;		//输入参数中是否有“-s”
	boolean needA;		//输入参数中是否有“-a”
	boolean needE;		//输入参数中是否有“-e”

###方法说明

String [] getFileName(String path)

返回 指定路径文件夹path中全部文件名数组(不含路径,含后缀)

void getAllFileName(String path,ArrayList<File> fileName)

递归获得 指定路径文件夹path 及其子文件夹中 全部文件名(含绝对路径,含后缀),结果存入fileName

void s(ArrayList<String> fileNames)

递归获得 指定路径文件夹(由类的static属性inputFile获得) 及其子文件夹中 全部符合用户要求的(也就是符合后缀名的)文件名(含绝对路径,含后缀),结果存入fileNames。

该方法会调用getAllFileName和getFileName方法。

void getBasicInfo(String fileName)

统计指定文件FileName的基本数据:字符数、单词数、行数,将其写入类的static属性chars/words/lines中(不带停用词表)

void getStopList(ArrayList<String> wordsIgnored)

获得指定停用词表stoplist中的单词,放在数组wordsIgnored中

void getBasicInfoWithSL(String fileName)

统计指定文件fileName的基本数据:字符数、单词数、行数,将其写入类的static属性chars/words/lines中(使用停用词表)

void getComInfo(String fileName)

统计指定文件fileName的复杂数据:代码行数、注释行数、空行数,将其写入类的static属性codeLines/comLines/empLines中

void main(String[] args)

主方法,整合全部功能。 #4、代码说明 ##停用词表: line是已经读取的输入文件的一行内容;

tm是已经放入了停用词的TreeMap实例。

将本行全部单词分割存储在str[]中。

遍历str[],若是改单词不在停用词表中,则单词数自增。

String reg1 = "\\s+|,+";   //用空格、tab或逗号来分割单词 
            	//String regEmp = "\\s+";
            	//将读取的文本进行分割 
            	String str[] = line.split(reg1); 
            	
            	for(String s: str)
            	{ 
            		if(!s.equals(""))
            		{
            			
               	     //判断tm即stopList中中是否已经存在该单词,若是不存在则单词数加一;若存在则不变
               			if(!tm.containsKey(s))   //tm中是否包含该单词
               			{ 
               				//System.out.println(s);
               				words++;
               			}
            		}
            		
            	 }

###代码行/注释行/空行判断

//_________________初始化和变量声明____________________
    	codeLines=0;
    	empLines=0;
    	comLines=0;
    	int comFlag=0;  //每遇到一个“/*”则+1,每遇到一个“*/”则减一;为正说明正处于多行注释之中
    	String regCom = "(\\s*)(\\{|\\})?(\\s*)(//|/\\*)([\\s\\S]*)";     //单行注释行或多行注释起始行的正则表达式
		String regEmp ="(\\s*)(\\{|\\}|;)?(\\s*)";            //空行的正则表达式

这段逻辑主要经过正则表达式进行基本判断;经过comFlag的变化,来判断多行注释。

下面这段代码完成了,统计字符串s中“/”和“/”的功能。

int i=0;
            			
            			while(i<s.length())
            			{
            				if(s.indexOf("/*",i)==i)
            				{
            					comFlag++;
            					
            					i+=2;
            				}
            				else if(s.indexOf("*/",i)==i)
            				{
            					comFlag--;
            					//System.out.print("i;m here");
            					//System.out.print(s.indexOf("*/",i));
            					//System.out.print(i);
            					i+=2;
            				}
            				else i++;

            			}

篇幅有限,完整函数请参考本篇开头的github网址。

###s()函数中,处理各类形式输入参数 咱们将用户输入的路径分为几种状况:

*.c 相对路径,无斜杠

src*.c 相对路径,有反斜杠

D:*.c 绝对路径,有反斜杠

src/*.c 相对路径,有正斜杠

D:/*.c 绝对路径,有反斜杠

测试发现,若是是无斜杠或有正斜杠的状况,main收到的参数将是该路径下,全部符合.c后缀名的文件名;如果含反斜杠,main收到的参数将原封不动的保留字符'*',但会将“/”自动变为“\”

咱们的s()方法:

void s(ArrayList<String> fileNames)

目标是,获得全部符合用户要求的文件名(含绝对路径,含后缀),存在fileNames中。

采用的方法是调用getAllFileName()方法。

getAllFileName(String path,ArrayList<File> fileName)

getAllFileName()会递归获得path路径下的全部文件和子文件夹中的文件名,存在fileName中。

如今的问题就是如何在调用getAllFileName前,获得正确的path参数。

咱们直接把inputFile分红含""和不含""的:

若含,则取斜杠以前的子串做为path;

若不含,则取path为“.”,也就是当前路径。

//若是 *.c 前面还有带有\的路径
		if(inputFile.indexOf('\\')>-1)
			{
				//System.out.println(inputFile);
				path=inputFile.substring(0,inputFile.lastIndexOf('\\'));
			}

		//若是参数是   *.c
		else path=".";

以后再调用getallfileNames便可。

###主函数参数判断 这段代码完成类的static属性赋值的任务。

因为对用户输入有要求,咱们直接认为:

o 参数以后输入的字符串,就是outputFile;

-e 以后输入的字符串,就是stopList;

若是一个参数,不是(-小写字母)的形式,且它前一个参数不是-e或-o,则认为该参数是inputFile。

inputFile="";
		for(int i=0;i<args.length;i++)
		{
			//System.out.println(args[i]);
			//判断参数状况
			switch(args[i])
			{
				case "-c" : needC=true;break;
				case "-w" : needW=true;break;
				case "-l" : needL=true;break;
				case "-o" : needO=true;outputFile=args[i+1];break;
				
				case "-s" : needS=true;break;
				case "-a" : needA=true;break;
				case "-e" : needE=true;stopList=args[i+1];break;
				
				default :
					if(!args[i-1].equals("-e")&&!args[i-1].equals("-o"))
						{
							
							inputFile=args[i];
						}
			}
			
		}

以后,根据参数状况,调用其余方法,将结果写入字符串outputStr;最后将outputStr写入输出文件(根据是否有-o参数,决定是写入默认的restult.txt仍是写入用户给定的文件)

#5、测试设计过程

##测试用例设计

按照白盒测试方法,重点考虑判断分支为风险较大处。

首先绘制程序图。

这时候发现了新问题:对于有必定规模的程序,若要绘制出 包含了全部判断分支 的程序图,一来工做量大,二来方便易用的绘图工具极其难找,三来画出的图将极其复杂,且且占面积极大,难以分析。

很好奇实际商用软件的测试,是如何解决这个问题的。

而对于咱们此次的WC,个人作法是,在设计用户输入的参数时,只考虑主函数中大体框架涉及的分支;而使用边缘测试的思想,设计inputFile和stoplist的内容。在绘制程序图方面,参考了王宇轩同窗的画法

绘制的程序图以下:(全部断定节点向左为yes,向右为no)

能够看到,只须要3个用例,便可穷尽每一个分支,达到断定/条件覆盖的要求。 红线是后文中的测试1,绿线是测试2,蓝线是测试3.

具体涉及的测试用例状况以下:(预期结果与实际运行结果彻底相同,请看后面的图)

编号 输入参数
1 wc.exe -s -c -a -w D:\testWC\WC*.c -e stoplist.txt -o D:\testWC\output.txt
2 wc.exe -s -l src*.c
3 wc.exe -a input1.c

此外,程序图的环复杂度为9,理论上须要9个独立的测试用例来覆盖每种条件的线性组合。与此同时,考虑不一样的文件名输入方式(相对路径,绝对路径)等,设计了剩下7个用例:

编号 输入参数
4 wc.exe -c input1.c
5 wc.exe -w input1.c
6 wc.exe -l input1.c
7 wc.exe -c D:\testWC\input1.c -o D:\testWC\output.txt
8 wc.exe -a D:\testWC\input1.c
9 wc.exe -l -s *.c
10 wc.exe -w D:/testWC/input1.c -e D:/testWC/stoplist.txt

测试用的输入文件input1.c 和 sub1\input.2 和 sub1\sub2\input3.c的内容均以下:

测试用的停用词表stoplist.txt 内容以下:

##测试脚本

直接采用以上10个测试用例,获得测试脚本test.txt内容以下:

wc.exe -s -c -a -w D:\testWC\WC\*.c -e stoplist.txt -o D:\testWC\output.txt
wc.exe -s -l sub1\*.c 
wc.exe -a input1.c

wc.exe -c input1.c  
wc.exe -w input1.c 
wc.exe -l input1.c 
wc.exe -c D:\testWC\input1.c -o D:\testWC\output.txt
wc.exe -a D:\testWC\input1.c
wc.exe -l -s *.c
wc.exe -w D:/testWC/input1.c -e D:/testWC/stoplist.txt

注意:因为该脚本测试了输入为绝对路径的状况,故在其余电脑上不可直接运行,上传到GitHub上的脚本是所有采用相对路径的。即:

wc.exe -s -c -a -w *.c -e stoplist.txt -o output.txt
wc.exe -s -l sub1\*.c 
wc.exe -a input1.c

wc.exe -c input1.c  
wc.exe -w input1.c 
wc.exe -l input1.c 
wc.exe -c input1.c -o output.txt
wc.exe -a input1.c
wc.exe -l -s *.c
wc.exe -w input1.c -e stoplist.txt

##测试结果

测试结果以下:

所有与预期状况相同。经过测试。

##测试评价 本次设计的设计用例,从主方法的大框架上,能够达到修正的断定/条件覆盖的要求;对于被统计文件的内容设计,则采用了边缘测试的思想,重点测试了除主方法外其余方法中,断定表达式的边缘状况。

整体来讲比较满意。

#6、参考文献链接:

java读写文件1 java读写文件2 递归获得全部文件名 java经过文件名判断文件类型 菜鸟教程-java正则表达式 菜鸟教程-java正则表达式 如何将java程序打包生成exe 廖雪峰老师的git教程 commit message的写法 Windows bat脚本如何运行

Written with StackEdit.

相关文章
相关标签/搜索