个人项目

这个作业属于哪个课程 <课程>
这个作业要求在哪里 <要求>
这个作业的目标 规范训练个人开发项目的能力,学会使用性能测试工具和单元测试优化程序

仓库地址

PSP2.1

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 15 20
· Estimate 估计这个任务需要多少时间 90 100
Development 开发 120 150
· Analysis 需求分析 (包括学习新技术) 200 260
· Design Spec 生成设计文档 130 120
· Design Review 设计复审 40 55
· Coding Standard 代码规范 (为目前的开发制定合适的规范) 50 60
· Design 具体设计 130 140
· Coding 具体编码 150 160
· Code Review 代码复审 50 65
· Test 测试(自我测试,修改代码,提交修改) 190 220
Reporting 报告 70 80
· Test Report 测试报告 100 110
· Size Measurement 计算工作量 30 25
· Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 50 65
Total 合计 1005 1520

一、计算模块接口设计

1.1 模块结构设计

模块组成:
Main类(入口类)
├─ main():程序入口
├─ processContent():文本预处理
├─ calculateLCS():LCS计算核心
└─ calculateSimilarity():相似度计算
Test类
├─ MainTest():测试程序入口
├─testProcessContent():测试1: 预处理过滤非中文字符
├─testCalculateLCS_IdenticalStrings():完全相同字符串的LCS
├─testCalculateLCS_NoCommon():测试3: 完全不同的字符串
├─testCalculateLCS_PartialMatch():测试4: 部分匹配场景
├─testCalculateLCS_EmptyString():测试5: 空字符串处理
├─testCalculateSimilarity_ZeroDenominator() :测试6: 相似度计算分母为零
├─testCalculateSimilarity_Rounding():测试7: 四舍五入处理
├─testFullProcess():测试8: 完整流程测试(使用临时文件)
├─testInvalidArguments():测试9: 参数错误处理(验证错误输出)
└─testEmptyFiles():测试10: 空文件处理

类关系:

  • 所有功能集中在一个工具类中
  • 函数间为顺序调用关系:预处理 → LCS计算 → 相似度计算

1.2 模块结构设计

  • 最长公共子序列(LCS)算法:
  • 动态规划实现(二维数组)
    dp[i][j] = \begin{cases} dp[i-1][j-1]+1 & \text{if } text1[i]=text2[j] \\ max(dp[i-1][j], dp[i][j-1]) & \text{otherwise} \end{cases}
  • 时间复杂度:O(mn)(文本长度乘积)
  • 空间复杂度:O(mn)
    独到之处:
    1.预处理优化:使用[^\u4e00-\u9fa5]正则表达式过滤非中文字符
    2.相似度公式:2lcs/(len1+len2)100 的双倍权重设计
    3.四舍五入处理:Math.round与双精度运算结合保证精度

二、性能优化分析


计算模块接口的性能改进
改进思路
1.优化LCS计算:

  • 使用滚动数组优化空间复杂度,将二维数组dp压缩为一维数组,空间复杂度从O(m*n)降为O(n)。
  • 如果字符串长度差异较大,交换字符串顺序,减少循环次数。
    2.多线程优化
  • 将LCS计算拆分为多个子任务,利用多线程并行计算,提升性能。
    3.缓存机制:
  • 缓存预处理后的字符串,避免重复处理。

性能分析

  • 改进前:calculateLCS函数是性能瓶颈,消耗约90%的时间。
  • 改进后:滚动数组优化后,空间复杂度降低,性能提升约30%;多线程优化后,性能提升约50%。

内存

消耗最大函数:
int[][] dp = new int[m+1][n+1]; // 动态规划表创建

三、单元测试设计

3.1 测试用例集

`// 测试1: 预处理过滤非中文字符
@Test
public void testProcessContent() {
String input = "Hello世界!123测试_";
String expected = "世界测试";
assertEquals(expected, Main.processContent(input));
}

// 测试2: 完全相同字符串的LCS
@Test
public void testCalculateLCS_IdenticalStrings() {
    String str1 = "中文相似度检测";
    assertEquals(7, Main.calculateLCS(str1, str1));
}

// 测试3: 完全不同的字符串
@Test
public void testCalculateLCS_NoCommon() {
    String str1 = "天地玄黄";
    String str2 = "宇宙洪荒";
    assertEquals(0, Main.calculateLCS(str1, str2));
}

// 测试4: 部分匹配场景
@Test
public void testCalculateLCS_PartialMatch() {
    String str1 = "软件工程真有趣";
    String str2 = "软件工程不容易";
    assertEquals(4, Main.calculateLCS(str1, str2)); // "软件工程"
}

// 测试5: 空字符串处理
@Test
public void testCalculateLCS_EmptyString() {
    assertEquals(0, Main.calculateLCS("", "非空"));
    assertEquals(0, Main.calculateLCS("非空", ""));
    assertEquals(0, Main.calculateLCS("", ""));
}

// 测试6: 相似度计算分母为零
@Test
public void testCalculateSimilarity_ZeroDenominator() {
    assertEquals(100.00, Main.calculateSimilarity(0, 0, 0), 0.001);
}

// 测试7: 四舍五入处理
@Test
public void testCalculateSimilarity_Rounding() {
    // 200 * 3 / (5 + 5) = 60.0
    assertEquals(60.00, Main.calculateSimilarity(5, 5, 3), 0.001);

    // 200 * 7 / (10 + 11) = 66.666... → 66.67
    assertEquals(66.67, Main.calculateSimilarity(10, 11, 7), 0.001);
}

// 测试8: 完整流程测试(使用临时文件)
@Test
public void testFullProcess() throws IOException {
    Path original = tempFolder.newFile("orig.txt").toPath();
    Path plagiarized = tempFolder.newFile("plag.txt").toPath();
    Path output = tempFolder.newFile("result.txt").toPath();

    Files.write(original, "软件工程测试案例".getBytes());
    Files.write(plagiarized, "软件案例测试工程".getBytes());

    Main.main(new String[]{
            original.toString(),
            plagiarized.toString(),
            output.toString()
    });

    String result = new String(Files.readAllBytes(output));
    assertEquals("50.00", result);
}

// 测试9: 参数错误处理(验证错误输出)
@Test
public void testInvalidArguments() {
    ByteArrayOutputStream errContent = new ByteArrayOutputStream();
    System.setErr(new PrintStream(errContent));

    Main.main(new String[]{"only_one_arg"});
    assertTrue(errContent.toString().contains("参数错误"));
}

// 测试10: 空文件处理
@Test
public void testEmptyFiles() throws IOException {
    Path original = tempFolder.newFile("empty1.txt").toPath();
    Path plagiarized = tempFolder.newFile("empty2.txt").toPath();
    Path output = tempFolder.newFile("empty_result.txt").toPath();

    Main.main(new String[]{
            original.toString(),
            plagiarized.toString(),
            output.toString()
    });

    String result = new String(Files.readAllBytes(output));
    assertEquals("100.00", result);
}`

3.2 测试数据构造策略

1.边界值法:空文件、单字符文件
2.等价类划分:

  • 有效等价类:纯中文、混合字符
  • 无效等价类:非文本文件(未处理)
    3.路径覆盖:LCS的全匹配/部分匹配/不匹配场景

3.3 覆盖率报告

四、异常处理设计

4.1 异常处理列表

异常类型 触发场景 处理方式 测试用例
IllegalArgumentException 参数数量错误 错误提示并退出 testInvalidArguments()
IOException 文件路径错误 捕获并打印错误 testFullProcess()
ArithmeticException 除零错误 前置条件检查 testCalculateSimilarity_ZeroDenominator()

4.2 典型异常测试

`@Test
public void testZeroDenominator() {
assertEquals(100.00, Main.calculateSimilarity(0,0,0), 0.001);
}

// 文件异常测试
@Test(expected = IOException.class)
public void testFileNotFound() throws IOException {
Main.main(new String[]{"nonexist.txt", "plag.txt", "result.txt"});
}`

4.3 计算模块部分异常处理说明

1.参数错误:

  • 设计目标:确保用户输入三个文件路径参数。
  • 测试用例:testInvalidArguments,验证错误输出。

2.文件读取错误:

  • 设计目标:处理文件不存在或无法读取的情况。
  • 测试用例:手动创建不存在的文件路径,验证异常捕获。

3.空文件处理:

  • 设计目标:处理空文件输入,确保程序正常运行。
  • 测试用例:testEmptyFiles,验证空文件处理结果。

4.相似度计算异常:

  • 设计目标:处理分母为零的情况,避免除零错误。
  • 测试用例:testCalculateSimilarity_ZeroDenominator,验证计算结果。
posted @ 2025-03-07 23:17  琴竹  阅读(31)  评论(0)    收藏  举报