软件测试第二周作业wordCount程序开发与测试总结
源代码GitHub链接:https://github.com/grinnings/wordCount
1、PSP表格
| PSP阶段 | 预计耗时(分钟) | 实际耗时(分钟) |
| 计划 | 30 | 60 |
| 估计这个任务需要时间 | 10 | 10 |
| 开发 | 4天 | 6天 |
| 需求分析(包括学习新技术) | 120 | 300 |
| 生成设计文档 | 60 | 120 |
| 设计复审 | 15 | 10 |
| 代码规范 | 15 | 15 |
| 具体设计 | 30 | 30 |
| 具体编码 | 3天 | 6天 |
| 代码复审 | 30 | 15 |
| 测试 | 30 | 15 |
| 报告 | 60 | 90 |
| 测试报告 | 50 | 60 |
| 计算工作量 | 15 | 15 |
| 事后总结并提出改进计划 | 60 | 60 |
| 合计 | 5天 | 6天 |
2、解题思路
拿到题目以后首先把整个需求阅读了一遍,把该需求简要地进行了一个提炼:即开发一个可以输入指令,具有文件读取、分析文件中数据、文件输出功能的程序,并对该程序进行测试。
然后我根据自己的总结,再对照原文快速核对了一遍,确认了该框架没有问题,接下来就先对该程序的各个模块分块解决。
具体去实现每个模块的时候,再对照老师给的作业要求,将需要注意的重要参数等设计开发过程中必需的要点记录下来。
最后边学习边实践,将各个模块逐个实现,进行测试。
3、程序设计实现过程
一下程序设计实现过程均以功能模块为划分来进行介绍。
首先按照用户思维,考虑做这个程序是要怎么用的,很自然会想到该程序的用户输入模块,程序如何获取用户输入的参数?考虑到参数较多,若使用程序内创建的动态数组来对输入参数进行分析,会加大对于输入数据处理的工作量,而且格式上还要求用户先输入一遍程序名称,用户使用体验较差,而且开发过程中对于该输入数组的处理较为繁琐。
所以我直接使用了Java的main函数自带的传参数组String[ ] args,这样便能使该处理过程简化一些,用户也可以直接在命令行输入参数,省去了再输入一次程序名称的繁琐。而从main函数传入的参数可以直接通过空格来分隔,不需要自己动手写方法来处理输入,能有效降低代码冗杂度,减少出Bug的概率。该部分功能的实现参考了以下互联网上查阅到的文章,链接如下,表示感谢:http://blog.csdn.net/chen825919148/article/details/8083250、http://blog.csdn.net/docuxu/article/details/73604038、http://blog.csdn.net/u013713294/article/details/53020293
第二部分,考虑的是程序获取用户输入的参数以后,进行参数分析的功能。先列出以下需要考虑实现的参数:
-c file.c //返回文件 file.c 的字符数 -w file.c //返回文件 file.c 的单词总数 -l file.c //返回文件 file.c 的总行数 -o outputFile.txt //将结果输出到指定文件outputFile.txt -s //递归处理目录下符合条件的文件 -a file.c //返回更复杂的数据(代码行 / 空行 / 注释行) -e stopList.txt // 停用词表,统计文件单词总数时,不统计该表中的单词
而最后的参数输入格式如下:
wc.exe -s -a –c -w *.c–e stop.txt –o output.txt
简单观察即容易发现,这些参数当中,“-e”指令和“-o”指令,均为后接文件名称的指令,可以理解为若args[i]中的是字符“-o”,则args[i+1]中是输出文件路径,否则报错;而其他指令都是属于单指令集,读取后只需要根据是否有该指令来决定是否进行相应操作即可,可以归入同一个指令集动态数组instructions;同时,作为源文件的名称则没有明确的位置限定,若不是上述两种指令,则可以先将其读取为源文件,随后再检测其作为源文件是否符合要求,若不是,则报错。
这样一来,我们便将输入简要地进行了分析分组,接下来要做的事情,就是根据我们分好的信息,从源文件中读取文件流,然后根据指令集,对源文件中的数据进行相应的词法分析,再将相应结果输出到目标文件。文件读写部分参考以下文章完成,链接如下,表示感谢:http://www.cnblogs.com/lovebread/archive/2009/11/23/1609122.html
进行完以上简要分析以后,就可以开始写文件读取部分的功能模块。这里我采取按行读取文件,然后按字节写出文件。同时,读取文件时采用寄存器缓存,提高读取效率。当读取成功后,就可以按行来对文件中的数据进行统计,然后再使用字符串将结果写入输出文件。至此,程序设计实现过程完毕,具体代码实现见下一部分。
4、代码说明
该部分将贴出部分核心功能的核心代码,进行简要讲解,功能类似的代码段仅列举一例进行讲解。
(1)获取输入参数进行分析,为传入wordCounter的实例进行准备,for循环按顺序处理每一个args内的字符串,并按照【程序设计实现过程】中的分析,对获取的字符串进行判断,将其赋值到相应的数据结构中去,这样就完成了第一步的参数分析。
// 获取参数
public static void getInstructions(String[] args) throws IOException
{
File content = new File("");
instructions = new ArrayList<String>();
absolutePath = content.getAbsolutePath() + "\\";//获取绝对路径
boolean flag = false;
for (int i = 0; i < args.length; i++) {
//无附加文件路径指令加入指令集
if (args[i].equals("-c") || args.equals("-w") || args.equals("-l") || args.equals("-a") || args.equals("-s"))
instructions.add(args[i]);
else if (args[i].equals("-o"))//输出参数-o获取输出路径
outPath = args[++i];
else if (args.equals("-e"))//停用词表-e获取停用词表路径
getDropList(args[++i]);
else//源文件路径
{
if (!flag)
sourceFile.add(new File(args[i]));
else {
String filter = args[i].substring(args[i].indexOf("."));
System.out.println(filter);
getFiles(absolutePath, filter);
}
}
}
}
(2)广度优先搜索(BFS)算法实现目录下所有文件的选取,在该方法内传入一个“filter”字符属性参数,对文件进行筛选。
//获取目录下文件路径
public static void getFiles(String fileContent, String filter) {
LinkedList<File> q = new LinkedList<File>();
q.addLast(new File(fileContent));
File temp = null;
while (!q.isEmpty())
{
temp = q.removeFirst();
if (temp.isDirectory())
{
for (File f : temp.listFiles())
q.addLast(f);
}
else if (isMatch(temp.getName(), filter))
sourceFile.add(temp);
}
}
(3)getDropList方法读取弃用词表文件,将其中的单词写入到DropList的动态数组中去。
public static void getDropList(String dropFile) throws IOException
{
InputStreamReader isRead = new InputStreamReader(new FileInputStream(absolutePath+dropFile));
BufferedReader buffer = new BufferedReader(isRead);
String string = buffer.readLine();
for (;string != null;)
{
String[] list = string.split(" ");
for (String l : list)
if(!Pattern.matches("\\s*",l))
dropList.add(l.toLowerCase());
}
}
(4)wordCounter的实例在接收到了传入的参数以后,便根据参数分析进行相应的文件操作。
//分析参数组
public void analyzeInstructions(ArrayList<String> instruction) throws IOException {
for (int i = 0; i<sourceFile.size(); i++)
{
//打开文件读取缓存
isRead =new InputStreamReader(new FileInputStream(sourceFile.get(i)));
buffer =new BufferedReader(isRead);
//指令组分析
if (instruction.contains("-c"))
countCharData(i);
if (instruction.contains("-w"))
countWordsData(i);
if (instruction.contains("-l"))
countLinesData(i);
if (instruction.contains("-a"))
countComplexData(i);
//关闭文件读写
isRead.close();
buffer.close();
outPut.close();
}
}
(5)以countCharData为例,若在上一步分析中发现指令集instruction中包含了"-c"指令,则按行读取文件中的数据并进行计数,将计算到的数据写入字符串中,再将字符串以byte的数据形式写到输出流中去。
//计数字符数
public void countCharData(int i) throws IOException
{
//初始化三个参数:字符数,文件流中的行数,输出结果
int charQuntity = 0;
String typeResult = null;
String string = buffer.readLine();
for (;string != null;)
charQuntity += string.length();
typeResult = sourceFile.get(i).getAbsolutePath().substring(absolutePath.length()) + ",字符数: " + String.valueOf(charQuntity) + "\r\n";
byte[] outData = typeResult.getBytes();
outPut.write(outData);
}
计数其他数据的思路与方法类似,后续不再举例。
5、测试设计过程
由于开发时间较为紧迫,没有较为详细去学习解测试设计的方法规范,感觉不太规范,下面按照自己的理解,进行一些简要的说明,贴出一部分代码。
根据编译器内断点调试的思路,如果需要测试一个功能模块,只要调用这部分功能,测其输出是否符合预期即可,但是这里对于输入的用例覆盖面如何做到较为全面地测试还不是很清楚。以下为部分测试代码,创建了一个测试用例对输入部分进行测试。
String[] info = {"-c","-w","-l","-a","./testCases","-e","./testCases/dropList","-o","./testCases/testResult1"};
Main mDemo = new Main();
//测试getInstructions
mDemo.getInstructions(info);
System.out.println(mDemo.sourceFile+","+mDemo.outPath+","+mDemo.dropList);
6、参考文献链接
Java主函数以及main函数传参详解 http://blog.csdn.net/chen825919148/article/details/8083250
Java main方法参数传递 http://blog.csdn.net/docuxu/article/details/73604038
IDEA中如何给main方法附带参数 http://blog.csdn.net/u013713294/article/details/53020293
Java读取文件方法大全 http://www.cnblogs.com/lovebread/archive/2009/11/23/1609122.html
Java统计文件中字符数、行数、单词数 http://blog.csdn.net/ycy0706/article/details/45457311
浙公网安备 33010602011771号