第一次个人编程作业
https://github.com/Guotao20050521/3123006072/tree/master
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477 |
这个作业的目标 | 对某种语言相应的测试工具使用有个初步的理解;熟悉用git管理和上传代码的步骤;熟悉PSP的有关内容 |
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 15 |
· estimate | · 估计这个任务需要多少时间 | 330 | - |
Development | 开发 | 240 | 300 |
· analysis | · 需求分析 (包括学习新技术) | 60 | 15 |
· design spec | · 生成设计文档 | 30 | 15 |
· design review | · 设计复审 | 15 | 5 |
· coding standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 15 |
· design | · 具体设计 | 30 | 30 |
· coding | · 具体编码 | 30 | 15 |
· code review | · 代码复审 | 5 | 5 |
· test | · 测试(自我测试,修改代码,提交修改) | 60 | 210 |
Reporting | 报告 | 30 | 25 |
·test report | ·测试报告 | 10 | 10 |
·size measurement | ·计算工作量 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 10 |
合计 | 280 | 340 |
一、模块接口的设计与实现
本项目用Java语言实现,包含PlagiarismChecker这一个类。类中各方法的功能如下表所示。
方法名 | 返回值 | 形参 | 功能 |
---|---|---|---|
main | void | String[] args | 主方法。读取参数,执行程序,输出结果 |
checkPlagiarism | double | String originalFilePath, String plagiarizedFilePath | 计算两个文件的相似度 |
readFile | String | String filePath | 读取文件内容 |
preprocessText | String | String text | 文本预处理,去除标点符号和空格,统一格式 |
calculateSimilarity | double | String text1, String text2 | 计算两个文本的相似度 |
calculateCharBasedSimilarity | double | String text1, String text2 | 基于字符的相似度计算 |
writeResult | void | double similarity, String answerFilePath | 将结果写入答案文件 |
compareTexts | double | String text1, String text2 | 用于单元测试的辅助方法,比较两个字符串的相似度 |
其中,calculateSimilarity()方法的流程图如下:
calculateCharBasedSimilarity()的流程图如下:
算法关键&独到之处:用字符交、并集合的大小比值(Jaccard相似度)来衡量重复程度。
二、模块接口部分的性能分析
用JVM分析工具VisualVM来分析应用程序。由于该程序运行速度较快,为保证程序能被VisualVM捕捉,在main()方法的开头加上了两行语句(已在提交时删去):
Scanner sc = new Scanner(System.in);
sc.nextLine(); //测试用,与功能实现无关
这样在程序运行后,需按Enter才会开始执行原有逻辑。性能分析图如下:
由该图可以看出:程序本身运行速度很快,最消耗时间的反而是为了控制速度而加的等待语句😂
内存分析如下:
由图可以发现,有个byte数组较占用空间,应是Java库代码所致。HashMap所占空间虽较小,但也值得留意。推测来自上述两个计算相似度的方法。此外还可以看出库代码中使用了反射机制(java.lang.reflect.Method)。
Java存在垃圾回收机制,该部分可视图如下:
由此看出程序执行中花了约11ms在垃圾回收中。
系统资源使用情况如下:
右上子图中堆大小有明显减小,是因为在VisualVM中点击了"Perform GC"导致。减小后,堆大小与程序实际占用较接近。
三、单元测试展示
1. 测试文本预处理功能
代码如下:
private static void testPreprocessText() {
System.out.println("开始测试文本预处理功能...");
// 测试用例1:包含标点符号和空格的文本
String text1 = "今天是星期天,天气晴,今天晚上我要去看电影。";
String expected1 = "今天是星期天天气晴今天晚上我要去看电影";
String result1 = preprocessTextForTest(text1);
assert result1.equals(expected1) : "测试用例1失败: 期望 '" + expected1 + "', 实际 '" + result1 + "'";
System.out.println("测试用例1通过");
// 测试用例2:包含英文和数字的文本
String text2 = "Hello, World! 123";
String expected2 = "helloworld123";
String result2 = preprocessTextForTest(text2);
assert result2.equals(expected2) : "测试用例2失败: 期望 '" + expected2 + "', 实际 '" + result2 + "'";
System.out.println("测试用例2通过");
System.out.println("文本预处理功能测试通过\n");
}
测试的方法是preprocessText()("ForTest"结尾的方法与被测试程序中的相同)。
2. 测试文本比较功能
private static void testCompareTexts() {
System.out.println("开始测试文本比较功能...");
// 测试用例1:完全相同的文本
String text1 = "今天是星期天天气晴";
String text2 = "今天是星期天天气晴";
double similarity1 = PlagiarismChecker.compareTexts(text1, text2);
assert Math.abs(similarity1 - 1.0) < 0.01 : "测试用例1失败: 期望 1.0, 实际 " + similarity1;
System.out.println("测试用例1通过:完全相同文本相似度为 " + String.format("%.2f", similarity1));
// 测试用例2:完全不同的文本
String text3 = "今天是星期天";
String text4 = "明天是星期一";
double similarity2 = PlagiarismChecker.compareTexts(text3, text4);
assert similarity2 >= 0 : "测试用例2失败: 相似度应该大于等于0, 实际 " + similarity2;
System.out.println("测试用例2通过:不同文本相似度为 " + String.format("%.2f", similarity2));
// 测试用例3:部分相同的文本
String text5 = "今天是星期天天气晴";
String text6 = "今天是周天天气晴朗";
double similarity3 = PlagiarismChecker.compareTexts(text5, text6);
assert similarity3 > 0 && similarity3 < 1 : "测试用例3失败: 相似度应该在0-1之间, 实际 " + similarity3;
System.out.println("测试用例3通过:部分相同文本相似度为 " + String.format("%.2f", similarity3));
System.out.println("文本比较功能测试通过\n");
}
测试的方法为compareTexts()。
3. 测试文件操作功能
private static void testFileOperations() {
System.out.println("开始测试文件操作功能...");
try {
// 创建测试文件
String originalFile = "original_test.txt";
String plagiarizedFile = "plagiarized_test.txt";
String answerFile = "answer_test.txt";
// 写入测试内容
writeTestFile(originalFile, "今天是星期天,天气晴,今天晚上我要去看电影。");
writeTestFile(plagiarizedFile, "今天是周天,天气晴朗,我晚上要去看电影。");
// 测试查重功能
double similarity = PlagiarismChecker.checkPlagiarism(originalFile, plagiarizedFile);
assert similarity >= 0 && similarity <= 1 : "文件操作测试失败: 相似度应该在0-1之间, 实际 " + similarity;
// 测试结果写入功能
PlagiarismChecker.writeResult(similarity, answerFile);
// 验证结果文件内容
String result = readTestFile(answerFile);
assert result.matches("\\d+\\.\\d{2}") : "结果文件格式错误: " + result;
// 清理测试文件
new File(originalFile).delete();
new File(plagiarizedFile).delete();
new File(answerFile).delete();
System.out.println("文件操作功能测试通过,结果文件内容: " + result + "\n");
} catch (Exception e) {
e.printStackTrace();
assert false : "文件操作测试异常: " + e.getMessage();
}
}
测试方法是writeResult()。
4. 测试覆盖率截图
测试报告有关内容在Github仓库中的"htmlReport"中。
四、计算模块异常处理
核心思路:
- 如果两个文本都为空,则相似度为1。
- 如果其中一个文本为空,则相似度为0。
在calculateCharBasedSimilarity()方法的最后一行,有语句:
return (double) intersection.size() / union.size(); //返回交集与并集的比值(Jaccard相似度)
若union.size()为0则会出现除0异常。为避免该异常,在这行语句前加上:
/*如果并集为空,说明两个集合都为空,相似度为1*/
if (union.isEmpty()) {
return 1.0;
}
测试样例可设置为两个传入的文件(原文或抄袭版论文)为空的情况。