這個作業屬於哪個課程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
---|---|
這個作業要求在哪裡 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13228 |
這個作業的目標 | 設計一個論文查重程式,瞭解軟體開發流程 |
gitHub專案地址 | https://github.com/Abaistudy/3122004760 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 30 |
Estimate | 估計這個任務需要多少時間 | 600 | 1220 |
Development | 開發 | 300 | 350 |
Analysis | 需求分析 (包括學習新技術) | 120 | 120 |
Design Spec | 生成設計文件 | 120 | 90 |
Design Review | 設計複審 | 20 | 30 |
Coding Standard | 程式碼規範 (為目前的開發制定合適的規範) | 20 | 30 |
Design | 具體設計 | 60 | 90 |
Coding | 具體編碼 | 180 | 150 |
Code Review | 程式碼複審 | 60 | 30 |
Test | 測試(自我測試,修改程式碼,提交修改) | 60 | 120 |
Reporting | 報告 | 120 | 180 |
Test Repor | 測試報告 | 20 | 10 |
Size Measurement | 計算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事後總結, 並提出過程改進計劃 | 10 | 10 |
合計 | 1120 | 1220 |
二、思路分析以及介面設計
題目回顧:
-
設計一個論文查重演算法,輸入一份原文檔案和一份抄襲版論文的檔案,計算重複率並在指定答案檔案中輸出。注意答案檔案中輸出的答案為浮點型,精確到小數點後兩位
-
輸入輸出格式:按照傳遞命令列引數的方式提供檔案的位置,需要從指定的位置讀取檔案,並向指定的檔案輸出答案。
python main.py [原文檔案] [抄襲版論文的檔案] [答案檔案]
執行環境:
PyCharm 2021.1.3 (Professional Edition)
python 3.9
思路分析:
主要介面設計
1.文字預處理(preprocess_text
):去除標點符號、換行符,使用 jieba 庫進行分詞,最後返回以空格分隔的詞語序列。
def preprocess_text(text):
"""
文字預處理:去除標點符號,換行符,並進行分詞
"""
# 避免輸入文字為空出錯
if not text:
return ""
# 去除標點符號和換行符
text = text.translate(str.maketrans('', '', string.punctuation + '\n\r\t'))
# 使用 jieba 進行分詞
words = jieba.lcut(text)
# 返回以空格分隔的詞語序列
return ' '.join(words)
2.文字向量化(vectorize_texts
):使用 TF-IDF 演算法將兩個文字向量化。
def vectorize_texts(text1, text2):
"""
使用 TF-IDF 將兩個文字向量化
"""
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform([text1, text2])
return tfidf_matrix
3.計算餘弦相似度(calculate_cosine_similarity
):可以使用 sklearn 裡面的函式計算向量化文字的餘弦相似度。
def calculate_cosine_similarity(tfidf_matrix):
"""
計算餘弦相似度
"""
if tfidf_matrix.shape[1] == 0: # 如果向量的維度為0,返回0相似度
return 0.0
# 計算兩個向量之間的餘弦相似度
similarity = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])[0][0]
return similarity
三、效能分析
使用cproflie
庫進行效能分析,生成performance_analysis_result檔案,使用snakeviz程式將效能分析結果進行視覺化。`
cProfile.run("plagiarism_check(orig_file, plagiarism_file, output_file)", filename="performance_analysis_result")
snakeviz.exe -p 8080 .\performance_analysis_result
效能分析結果如圖:
可見在主函式plagiarism_check
中,函式preprocess_text
執行時間最長。
四、單元測試
測試點分析
1.測試檔案的讀寫功能
測試點:
-
已存在的檔案能否正確讀取
-
讀取不存在的檔案時能否提示錯誤
-
得到的相似度結果能否正確儲存
測試程式碼
def test_read_file_existing(self):
"""
1.1: 測試能否正確讀取已存在的檔案
預期:text非空
"""
test_file = './test_text/orig.txt'
text = main.read_file(test_file)
assert text is not None
def test_read_file_not_found(self):
"""
測試點 1.2: 測試讀取不存在的檔案
預期:報錯,並提示錯誤檔名
"""
# 404.txt 不存在
test_file = './test_text/404.txt'
with pytest.raises(FileNotFoundError):
main.read_file(test_file)
def test_save_similarity_to_file(self):
"""
測試點 1.3: 測試能否正確儲存相似度結果(即輸出內容寫入檔案)
預期:讀取檔案內容與寫入時一致
"""
test_output_file = './test_text/test_output.txt'
main.save_similarity_to_file(test_output_file, 0.75)
with open(test_output_file, 'r', encoding='utf-8') as f:
result = f.read()
assert result == '0.75'
os.remove(test_output_file)
2.測試文字預處理功能
測試點:
-
正常文字能否正確處理
-
空文字能否正確處理
-
亂碼文字能否正常處理
測試程式碼
def test_preprocess_text_normal(self):
"""
測試點 2.1: 測試正常文字預處理
預期:返回分詞後字串
"""
text = "你好,世界!這是一個測試。"
result = main.preprocess_text(text)
assert result == "你好 世界 這是 一個 測試"
def test_preprocess_empty_text(self):
"""
測試點 2.2: 測試空文字處理
預期:返回空字串
"""
text = ""
result = main.preprocess_text(text)
assert result == ""
3.測試文字向量化功能
測試點:
- 正常文字能否正確向量化
測試程式碼
def test_vectorize_texts_normal(self):
"""
測試點 3.1: 測試正常文字向量化
預期:與計算結果一致
"""
text1 = "這是一個測試"
text2 = "這是另一個測試"
tfidf_matrix = main.vectorize_texts(text1, text2)
assert tfidf_matrix.shape == (2, 2) # 2 行(文字),2列(詞彙數)
4.測試餘弦相似度計算功能
測試點:
- 不同文字的相似度計算是否正確(小於1)
- 相同文字的相似度計算是否正確(等於1)
- 完全不同文字(或空文字)的相似度計算是否正確(極小,小於0.1)
測試程式碼
def test_calculate_similarity_normal(self):
"""
測試點 4.1: 測試不同文字的相似度計算
預期:返回一個不大於1的浮點數
"""
text1 = "這是一個測試"
text2 = "這是另一個測試"
text1 = main.preprocess_text(text1)
text2 = main.preprocess_text(text2)
tfidf_matrix = main.vectorize_texts(text1, text2)
similarity = main.calculate_cosine_similarity(tfidf_matrix)
assert 0 <= similarity <= 1
def test_calculate_similarity_identical(self):
"""
測試點 4.2: 測試相同文字的相似度計算
預期:返回1.0
"""
text1 = "這是一個測試"
text2 = "這是一個測試"
text1 = main.preprocess_text(text1)
text2 = main.preprocess_text(text2)
tfidf_matrix = main.vectorize_texts(text1, text2)
similarity = main.calculate_cosine_similarity(tfidf_matrix)
similarity = round(similarity, 2)
assert similarity == 1.0
def test_calculate_similarity_completely_different(self):
"""
測試點 4.3: 測試完全不同文字(或空文字)的相似度計算
預期:返回的餘弦相似度極小,小於0.1
"""
text1 = "這是一個測試"
text2 = "完全不同的文字"
text3 = ""
tfidf_matrix1 = main.vectorize_texts(text1, text2)
tfidf_matrix2 = main.vectorize_texts(text1, text3)
similarity1 = main.calculate_cosine_similarity(tfidf_matrix1)
similarity2 = main.calculate_cosine_similarity(tfidf_matrix2)
assert similarity1 < 0.1
assert similarity2 == 0.0
5.測試主流程功能
測試點:
-
主函式流程是否能正常執行
-
缺少命令列引數的情況能否處理
-
多於3個命令列引數的情況能否處理
測試程式碼
def test_main_flow(self):
"""
測試點 5.1: 測試主函式
預期:各個斷言透過
"""
orig_file = './test_text/orig.txt'
plagiarism_file = './test_text/orig_0.8_add.txt'
output_file = './test_text/orig_output.txt'
orig_file = main.read_file(orig_file)
plagiarism_file = main.read_file(plagiarism_file)
assert orig_file is not None
assert plagiarism_file is not None
tfidf_matrix = main.vectorize_texts(orig_file, plagiarism_file)
similarity = main.calculate_cosine_similarity(tfidf_matrix)
assert 0 <= float(similarity) <= 1
main.save_similarity_to_file(output_file, similarity)
with open(output_file, 'r', encoding='utf-8') as f:
result = f.read()
assert result == str(round(similarity, 2))
def test_missing_arguments(self):
"""
測試點 5.2:模擬缺少命令列引數的情況
預期:退出碼非正常
"""
orig_file = './test_text/orig.txt'
plagiarism_file = './test_text/orig_0.8_add.txt'
output_file = './test_text/orig_output.txt'
# 使用 os.system 執行命令,少傳遞一個引數來模擬缺少引數的情況
exit_code = os.system(f'python main.py {orig_file} {plagiarism_file}')
# 預期程式返回非零狀態碼,因為命令列引數不足
assert exit_code != 0 # os.system() 返回的非零程式碼表示錯誤
def test_extra_arguments(self):
"""
測試點 5.3:模擬多於3個命令列引數的情況
預期:退出碼非正常
"""
orig_file = './test_text/orig.txt'
plagiarism_file = './test_text/orig_0.8_add.txt'
extra_file = "./test_text/orig_0.8/del.txt"
output_file = './test_text/orig_output.txt'
# 使用 os.system 執行命令,多傳遞一個引數來模擬過多引數的情況
exit_code = os.system(f'python main.py {orig_file} {plagiarism_file} {extra_file} {output_file}')
# 預期程式返回非零狀態碼,因為命令列引數過多
assert exit_code != 0 # os.system() 返回的非零程式碼表示錯誤
覆蓋率
五、異常處理
1.讀取出錯異常FileNotFoundError
原因分析:檔案路徑錯誤或檔案不存在
def read_file(file_path):
if not os.path.exists(file_path):
raise FileNotFoundError(f"檔案路徑錯誤或檔案不存在: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
raise IOError(f"讀取檔案時出錯: {e}")
2.寫入異常IOError
原因分析:可能是由於許可權問題無法進行寫入
def save_similarity_to_file(output_file, similarity):
"""
將相似度結果儲存到指定檔案,保留兩位小數
"""
try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(f"{similarity:.2f}")
except Exception as e:
raise IOError(f"寫入檔案時出錯: {e}")
六、測試報告
使用allure生成測試報告如圖
# 命令列輸入
pytest main.py --alluredir result
# 最後一個路徑指定了json檔案輸出的目錄
allure generate result -o resport_allure --clean
# 從json檔案路徑中提取資料,輸出一個html網頁到指定路徑
allure open resport_allure
# 開啟測試報告網頁