软件测试第二周作业 WordCounter

Posted vectorlu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件测试第二周作业 WordCounter相关的知识,希望对你有一定的参考价值。

Github 项目地址

WordCounter in github

PSP(Personal Software Process)

PSP2.1 PSP阶段 预估耗时实际耗时(分钟) 实际耗时(分钟)
Planning 计划 10 17
Estimate 估计这个任务需要多少时间 5 10
Development 开发 545 650
- Analysis - 需求分析(包括学习新技术) 120 160
- Design Spec - 生成设计文档 60 90
- Coding Standard - 代码规范 (为目前的开发制定合适的规范) 5 10
- Design - 具体设计 30 30
- Coding - 具体编码 180 210
- Code Review - 代码复审 30 30
- Test - 测试(自我测试,修改代码,提交修改) 120 120
Reporting 报告 105 110
- Test Report - 测试报告 60 90
- Size Measurement - 计算工作量 15 20
- Postmortem & Process Improvement Plan - 事后总结, 并提出过程改进计划 30 232
合计 665 787

解题思路

由于最近事情比较多,而且之前做过的词法分析器与之有很多类似的地方,可以复用解法。目测需求可能有一定的变更,预估完成时间应该不长,较晚才开始着手 Orz。

题目解读:精确定义问题和处理方法

实际上,要做一个代码数目统计的程序也并不简单,因为要了解不同的语言的一些语法特性。要做一个能统计所有种类的代码的数量的程序,会非常麻烦。譬如下图所示,许多现代语言的字符集的支持非常丰富,而采取的编码和计数方式缤纷多彩。
技术分享图片

幸运的是,仔细阅读了老师的题目说明后——了解到老师的要求是:

一个用来检测只包含 ASCII 编码的 .c,.h 等文件的统计工具。

没有中文字符是在讨论区的答疑中看到的,故我个人目前详细定义为只包含 ASCII 编码

其中 word 的统计方法为了简化工作,采用了比较费解的统计方法——这也导致了方便检测 .sh 类的文件(其语法中很多地方不允许空格分隔),故详细定义为 .c ,.h 等文件。还有一个问题是在代码中的字符串中如果出现 \\n,而代码本身没有换行,只算作字符,那么是算作转义字符,还是算两个正常字符呢?如果需要检测其他语言,像 .py 中单引号字符串 ‘‘,双引号字符串"" 和多行字符串 """""" 是否需要支持,多行字符串是算作代码呢?还是算作注释呢?其行数又该如何定义和计算呢?

由于其他语言还涉及到一系列复杂的问题,所以我个人详细定义为只处理 .c 和 .h 文件

  1. 转义字符的问题已提交讨论区,老师说不考虑转义字符。
  2. CRLF 字符的问题已提交讨论区,已找到解决方法。

跨平台也带来一些麻烦,对于换行,Windows 使用的是 CRLF \\r\\n,而 macOS 等主流类 Unix 系统使用的是 \\n。在计算字符时应该如何处理。这些也没有在题目中详细定义。暂时只把这些问题交给 Java 本身。

最终的测试脚本,如果没有参数,或参数不合法,需要报怎样的错误提示,还是仅仅什么都不干,没有明确定义。如果我们在程序中写了提示,是否会导致老师测试失败,从而零分。

思路

基础功能

  1. 读取字符数
  2. 读取单词数
  3. 读取行数
  4. 输出文件

计数比较简单。如果不需要考虑处理字符串的问题,可以直接使用例如 aString.length() 等方法获取信息。如果需要考虑,就要便利字符串,分析计数。

扩展功能

  1. -s 需要使用递归实现文件夹的遍历
  2. -a 需要分析语法信息,用 DFA 实现
  3. -e 需要创建一个屏蔽词的数据结构,在统计 word 时跳过这些屏蔽词

代码说明

由于许多功能没有明确定义,目前看来,基础功能比较简单,就放在了一个类中 WordCounter。用各种方法来组织代码。

重要变量说明

/**
 * 基本功能的命令参数
 */
boolean requestChar = false; // -c
boolean requestWord = false; // -w
boolean requestLine = false; // -l
// -o outputFile.txt 必须成对出现
boolean requestOut = false;
String outputFileName = null;

/**
 * 扩展功能的命令参数
 */
// -s 是否需要递归处理目录下符合条件的文件
boolean requestRecur = false;
// -a 是否需要返回更复杂的数据(代码行 / 空行 / 注释行)
boolean requestMore = false;
// -e stopList.txt 是否需要提供文件,不计入单词统计
boolean requestIgnore = false;
String stopListFile = null;

// 匹配 .c & .h 文件的正则表达式
String countableFilePattern = "(\\\\w)+.[ch]";
String txtPattern = "(\\\\w)+.txt";

WordCounter wc = new WordCounter();

// 需要计算的所有文件名,支持多个文件
ArrayList<String> countFileNames = null;
// -s 支持递归查看一个文件目录下的所有文件
String folderName = null;

boolean inString = false;

依赖方法说明

/**
     * 保留的空构造方法
     */
    public WordCounter(){}

    /**
     * 判断是否是各种空白字符
     * @param ch
     *        The char to be tested.
     * @return {@code true} if {@code ch} is blank, otherwise {@code false}
     */
    private boolean isBlank(char ch) {
        if (ch==‘ ‘ || ch==‘\\t‘ || ch==‘\\n‘) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 判断文件名是否和要求的正则表达式相匹配
     * @param filename
     *        被判断的文件名
     * @param filePattern
     *        正则匹配格式
     *        "(\\\\w)+.txt" *.txt, "(\\\\w)+.[ch]" *.c or *.h
     * @return {@code true} if the file can be countable by the program,
     *         otherwise {@code false}
     */
    private boolean isFileMatch(String filename, String filePattern) {
        // .c & .h 文件的文件名正则表达式
        Pattern pattern = Pattern.compile(filePattern);
        // 名字为空显然不匹配
        if (filename == null || filename.equals("")) {
            return false;
        }
        Matcher matcher = pattern.matcher(filename);
        return matcher.matches();
    }

功能方法说明

篇幅原因,只给出函数原型,详情请见源代码。

// 读取行数
private long countLine(File countF) 

// 读取单词数
private long countWord(File countW) 

// 构造 stopList 的数据结构
private Hashset<String> createStopList(String fileName)
// 按 stopList.txt 读取单词数
private long countCharWithIgnore(File countW) 

// 读取字符数
private long countChar(File countC) 

// 递归获取一个目录下的所有文件
private ArrayList<File> getAllFiles(String path) 

// 获取代码行 / 空行 / 注释行
private int[] getMoreInfo(File countM)

测试设计过程

测试原理

采用课程中介绍的白盒测试用例设计方法来设计测试用例,在测试用例说明中都有具体的体现:

  1. 精确到函数来设计用例
  2. 测试边界
  3. 路径测试
    技术分享图片

单元测试

单元测试的结构

创建单元测试函数的主要步骤:

  1. 设置数据(一个假想的正确的E-mail地址);
  2. 使用被测试类型的功能(用E-mail地址来创建一个User类的实体);
  3. 比较实际结果和预期的结果(Assert.IsTrue(target!= null);)

记录测试用例

  1. 使用易读的测试用例名,见名知意。如最小/空测试 test/testCases/empty.c
  2. 在项目的 README.md 中详细说明。如最大单文件测试,来自 Lua 语言源代码中的解析器 test/testCases/lparser.c
  3. 在项目的 commit 中也写了测试概要

程序高风险说明

  1. 读写文件权限问题,如果在类 Unix 环境下,可能无写权限,导致 IOException
  2. 递归处理文件夹问题,由于使用了递归方法处理文件夹,如果路径过深,可能导致栈溢出。
  3. 文件创建失败,或不存在异常处理等

测试用例说明

下列共 14 个测试文件,基本覆盖了所有规定的、可能的路径。

  • test
    • testCases
      • 1 emptyTest.c // 测试空文件 最小测试
      • 2 hello.c // 普通程序测试
        // 3 业界代码实况测试——Lua 语言的 parser 代码,50 KB,接近 2000 行
      • 3 lparser.c
      • 4 quoteTest1.c // 4 单独成行的注释测试
      • 5 quoteTest2.c // 5 多行代码注释
      • 6 quoteInString.c // 6 当 注释符号出现在字符串中,并不认为它是注释
      • 7 newLine.c // 7 单个空行测试
      • 8 newLines.c // 8 多个空行测试
      • 9 crlfLfF.c // 9 跨平台换行测试
      • 10 stopEmpty.c // 10 停用词表为空测试
      • 11 stopList.c // 11 停用词表非空测试
      • 12 iLegalStopList 12 非法停用词表测试
      • 13 sqllite.txt // 13 ASCII C 工业项目下载地址 SQLLite 获取一个路径下的所有文件测试
      • 14 deepRecur // 14 深目录测试

参考资料

  1. Java 白皮书 version 9 & version 10
  2. 《Java 测试驱动测试》—— 图灵出版社
  3. Java SE API
  4. 邹欣老师关于单元测试的博客




以上是关于软件测试第二周作业 WordCounter的主要内容,如果未能解决你的问题,请参考以下文章

第二周作业

20165306课下作业(第二周)

软件项目管理第二周作业

软件过程与项目管理第二周作业

第二周作业

《实时控制软件设计》第二周作业