软件工程导论作业2:论文查重
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/ |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13229 |
这个作业的目标 | 通过开发个人项目,实现项目单元测试 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
Estimate | 估计这个任务需要多少时间 | 900 | 1000 |
Development | 开发 | 420 | 600 |
Analysis | 需求分析 (包括学习新技术) | 30 | 25 |
Design Spec | 生成设计文档 | 20 | 20 |
Design Review | 设计复审 | 20 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 35 |
Design | 具体设计 | 50 | 40 |
Coding | 具体编码 | 50 | 70 |
Code Review | 代码复审 | 20 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 40 | 60 |
Reporting | 报告 | 20 | 30 |
Test Repor | 测试报告 | 40 | 60 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 780 | 1020 |
二、模块接口的设计与实现过程
模块接口设计
类和函数设计
- DulcateCheckerMain 类:作为主类,负责处理命令行参数,调用文件读取、文本处理和结果输出。
主要方法:main
,处理命令行参数,调用其他类的方法来执行具体任务。 - FileProcess 类:负责文件的读取和写入。
方法:readFile
读取文件内容,writeResult
将处理结果写入文件。 - TextProcess 类:负责文本处理,包括分词和计算Jaccard相似度。
方法:tokenize
对文本进行分词,calculateJaccardSimilarity
计算两个词集的Jaccard相似度。
实现过程
关系
类名 | 方法名 | 职责描述 |
---|---|---|
DulcateCheckerMain | main | 程序入口,处理命令行参数,调用其他类方法执行任务。 |
FileProcess | readFile | 读取文件内容并返回字符串。 |
writeResult | 将相似度结果写入指定文件。 | |
TextProcess | tokenize | 对文本进行分词,返回词集合。 |
calculateJaccardSimilarity | 计算两个词集合的Jaccard相似度。 |
流程图
graph TD
A[开始] --> B{检查命令行参数}
B -->|参数正确| C[读取原始文件]
B -->|参数错误| D[输出使用说明]
D --> E[结束]
C --> F[读取疑似抄袭文件]
F --> G[分词处理]
G --> H[计算Jaccard相似度]
H --> I[写入结果到文件]
I --> J[异常处理]
J --> E
算法的关键
- 分词算法:使用
ToAnalysis.parse
方法对文本进行分词,这是文本处理的基础。 - Jaccard相似度计算:计算两个词集的交集和并集,然后计算相似度。这是核心算法,用于评估文本的相似度。
三、性能分析
使用性能分析工具(如 Visual Studio 2017 的性能分析器或 JProfiler)来识别程序中的性能瓶颈
已删除黄色提示
四、单元测试
测试说明
DulcateCheckerMainTest
主程序测试
testMain
: 这个测试方法验证了 DulcateCheckerMain 类的 main 方法能否正确处理文件读取、文本比较,并正确写入结果。测试数据包括两个临时文件,一个作为原始文本,另一个作为疑似抄袭文本。
testExceptionHandling
: 这个测试方法检查当输入参数不正确时(例如,其中一个参数为 null),程序是否能抛出异常。FileProcessTest
文件处理测试
testReadFile
: 验证 readFile 方法能否正确读取文件内容。测试中创建了一个临时文件,并写入了测试数据,然后通过 readFile 方法读取并验证内容。
testWriteResult
: 验证 writeResult 方法能否将结果正确写入到文件中。测试中创建了一个临时文件,并通过 writeResult 方法写入了一个测试结果,然后读取并验证文件内容。TextProcessTest
文本处理测试
testTokenize
: 这个测试方法验证 tokenize 方法能否正确分词。测试中使用了一段包含多种字符的文本,验证分词结果是否包含了所有预期的词汇。
testCalculateJaccardSimilarity
: 验证calculateJaccardSimilarity
方法能否正确计算两个词集的Jaccard相似度。测试中创建了两个词集,并验证计算结果是否符合预期。
构造测试数据的思路
- 简单性:测试数据应尽可能简单,以减少测试的复杂性,同时覆盖所有重要的场景。
- 全面性:测试数据应覆盖所有可能的输入情况,包括正常情况和边界情况。
- 可重复性:测试数据应能在任何时候重复使用,以确保测试结果的一致性。
- 独立性:每个测试应独立于其他测试,以确保一个测试的失败不会影响到其他测试。
测试函数
主函数部分单元测试
public class DulcateCheckerMainTest {
//读取文件、方法测试
@Test
public void testMain() throws Exception {
File origFile = File.createTempFile("orig", ".txt");
File plagFile = File.createTempFile("plag", ".txt");
File resultFile = File.createTempFile("result", ".txt");
try (PrintWriter writer = new PrintWriter(new FileWriter(origFile))) {
writer.println("This is the original text.");
}
try (PrintWriter writer = new PrintWriter(new FileWriter(plagFile))) {
writer.println("This is the plagiarized text.");
}
String[] args = {origFile.getAbsolutePath(), plagFile.getAbsolutePath(), resultFile.getAbsolutePath()};
DulcateCheckerMain.main(args);
try (BufferedReader reader = new BufferedReader(new FileReader(resultFile))) {
String line = reader.readLine();
assertNotNull(line);
assertTrue(line.matches("\\d+\\.\\d+"));
}
origFile.deleteOnExit();
plagFile.deleteOnExit();
resultFile.deleteOnExit();
}
//异常处理测试
@Test
public void testExceptionHandling() throws IOException {
File plagFile = File.createTempFile("plag", ".txt");
File resultFile = File.createTempFile("result", ".txt");
try (PrintWriter writer = new PrintWriter(new FileWriter(plagFile))) {
writer.println("This is the plagiarized text.");
}
String[] args = {null, plagFile.getAbsolutePath(), resultFile.getAbsolutePath()};
assertThrows(Exception.class, () -> {
DulcateCheckerMain.main(args);
});
}
}
文件处理单元测试
public class FileProcessTest {
//读文件
@Test
public void testReadFile() throws IOException {
File tempFile = File.createTempFile("testRead", ".txt");
try (PrintWriter writer = new PrintWriter(new FileWriter(tempFile))) {
writer.println("This is a test file.");
}
String content = FileProcess.readFile(tempFile.getAbsolutePath());
assertEquals("This is a test file.\n", content);
tempFile.deleteOnExit();
}
//写文件
@Test
public void testWriteResult() throws IOException {
File resultFile = File.createTempFile("testResult", ".txt");
FileProcess.writeResult(resultFile.getAbsolutePath(), 0.85);
try (BufferedReader reader = new BufferedReader(new FileReader(resultFile))) {
String line = reader.readLine();
assertNotNull(line);
assertEquals("0.85", line);
}
resultFile.deleteOnExit();
}
文本处理单元测试
public class TextProcessTest {
//分词测试
@Test
public void testTokenize() {
String text = "你好,我是哈哈。";
Set<String> tokens = TextProcess.tokenize(text);
assertTrue(tokens.contains("你好"));
assertTrue(tokens.contains(","));
assertTrue(tokens.contains("我"));
assertTrue(tokens.contains("是"));
assertTrue(tokens.contains("哈哈"));
assertTrue(tokens.contains("。"));
}
//相似度计算测试
@Test
public void testCalculateJaccardSimilarity() {
Set<String> set1 = new HashSet<>();
set1.add("a");
set1.add("b");
set1.add("c");
Set<String> set2 = new HashSet<>();
set2.add("b");
set2.add("c");
set2.add("d");
double similarity = TextProcess.calculateJaccardSimilarity(set1, set2);
assertEquals(0.5, similarity, 0.01);
}
}
五、异常处理
文件不存在异常(FileNotFoundException)
设计目标:在文件路径指向不存在的文件时,应通知用户文件无法找到,避免程序进一步执行错误的操作。
单元测试样例:
//异常处理测试
@Test
public void testExceptionHandling() throws IOException {
File plagFile = File.createTempFile("plag", ".txt");
File resultFile = File.createTempFile("result", ".txt");
try (PrintWriter writer = new PrintWriter(new FileWriter(plagFile))) {
writer.println("This is the plagiarized text.");
}
String[] args = {null, plagFile.getAbsolutePath(), resultFile.getAbsolutePath()};
assertThrows(Exception.class, () -> {
DulcateCheckerMain.main(args);
});
}
}
错误场景:文件路径错误或文件已被删除,但程序仍尝试读取它。
测试结果