亓雪冬, 葛元康
(中國石油大學(華東) 1.信息化建設處; 2.計算機科學與技術學院, 山東 青島 266580)
近年來,Python語言以其簡潔的語法、靈活的交互性與動態性和豐富的第三方程序庫等特點快速被國內外高校所認可。嵩天等提出,Python語言是程序設計課程教學改革的理想選擇[1]。眾多高校中開設了大量以Python語言為載體的程序設計類課程,以我校為例,2019—2020學年約12個專業(20多個班級)在全校公共基礎課《程序設計》課程中,將授課編程語言改變為Python。新的選擇給教學工作帶來機遇的同時也帶來了挑戰。
程序自動評測系統能夠快速對學生編寫的程序進行自動評判[2-3],實現了批改和反饋的自動化[4-5],是程序設計類課程教學實踐環節不可或缺的教學工具,是提高學習效率、保障教學效果和提升教學質量的重要技術手段。因此,研究Python程序自動評判方法對于教學改革具有重要的現實意義。本文在充分研究Python標準庫原有測試模塊doctest的基礎上,根據教學實踐環節的實際需求,提出了擴展和改造doctest編程模型的思路和方法,實現了Python程序的全自動評判。
doctest是Python標準庫中的自動化測試模塊,根據編寫在文檔字符串中的測試用例對待測試模塊的功能進行測試。文檔字符串(DocStrings)是Python模塊、類或函數定義中的起始語句,可看作多行注釋,定界符為三個單引號(' ' ')或者三個雙引號(""")。將測試用例放入文檔字符串中,易于查看和修改。測試用例格式與Python交互式環境中執行語句的格式完全一致。
doctest的核心測試功能由2個容器類和4個處理類提供。對象模型[6],如圖1所示。

圖1 doctest對象模型
兩個容器類分別為Example和DocTest,用于存儲測試文本中提取的測試用例。其中,Example表示單個測試用列,由一條Python語句和其對應的輸出組合而成;DocTest表示Example(測試用例)的集合。
四個處理類分別為DocTestFinder、DocTestParser、DocTestRunner和OutputChecker。其中,DocTestFinder用于發現測試模塊中的文檔字符串;DocTestParser用于分析并返回文檔字符串中的測試用例集合DocTest;DocTestRunner控制執行DocTest中每一個測試用例,同時調用OutputChecker檢查用例的輸出,最后統計并輸出測試結論。
(1) 描述測試用例
例如對于求取n階乘的函數factorial(n),設計測試用例對其進行測試。程序結構,如圖2所示。

圖2 包含doctest測試用例的程序結構
程序上部的文檔字符串描述了對factorial(n)進行測試的測試用例,程序下部為factorial函數的定義。
測試用例的設計上,根據階乘定義factorial(n)=1*2*…*n,可選取n≥1的正整數作為測試用例,如n=10時,階乘為3 628 800。另外對于特殊情況n=0時,階乘為1,這一點經常有同學出錯,這個特例也可作為一個測試用例。每個測試用例中,三個右箭頭(>>>)起始的行表示待執行的Python語句,隨后的行表示期待的正確輸出結果,格式與在IDLE或Python Shell中執行Python語句類似。
(2) 執行測試
上述程序代碼存儲在文件example.py中。使用doctest對其執行測試的命令如下。
import doctest, example
doctest.testmod(example)
第1句導入測試工具doctest和用戶程序example.py。example中不僅包含了階乘函數的定義,而且包含了對其進行測試的兩個測試用例。第2句調用doctest.testmod函數對example模塊進行測試。測試過程如1.1節描述,包含發現和分析測試用例,執行測試用例,返回測試結果等步驟。
(3) 返回測試結果
假設編寫的factorial函數沒有考慮0的階乘這種特殊情況,第1個測試通過,第2個測試失敗。返回的測試結果,如圖3所示。

圖3 測試結果
測試結果整體分為兩部分,上部為對每一個測試用例的詳細描述,如果測試失敗,還會給出程序實際輸出的錯誤結果以及期待的正確結果;下部為整體測試結論,包含測試數目以及測試失敗的數目。
doctest雖具備較完整的程序測試功能,但程序測試與教師對學生程序的評測這兩者概念不完全相同,將doctest應用在Python課程的作業和考試評測環節中時,仍存在一些不足,主要體現在以下三個方面。
(1) 測試用例的部署。doctest中的測試用例與被測試代碼在同一個文件中。如果在學生編寫程序的文件中提前設置好測試用例,由于學生可以看到測試用例,會降低考查的難度甚至失去考查的意義。因此需要在獲得學生提交的程序之后,動態部署教師設置的測試用例,然后再進行評測。
(2) 測試用例無權重設定。doctest測試用例中沒有權重屬性,這將無法區分不同測試用例的重要程度。然而在教學實踐中,教師往往會根據授課內容的先后順序、難易程度和課程目標等,調整不同知識點對應的分值。對應到程序評測中,則需要能夠對較重要的測試用例設置較高的權重,而對相對次要的測試用例設置較低的權重。
(3) 評測結果難于理解。doctest的測試結果缺乏測試用例的編號項,基于文本的輸出形式布局過于簡潔,對初學程序的學生來說難于理解。教學實踐中,需將評測結果修改為HTML格式,每行描述一個測試用例,方便學生查看,并易于和其它Web教學系統結合。
針對以上三點不足,筆者嘗試通過擴展doctest對象模型加以解決。通過擴展DocTestFinder類實現測試用例的動態部署;通過擴展Example類和DocTestParser類實現測試用例權重的設定;通過擴展DocTestRunner類實現評測結果以HTML格式進行輸出。
為了能夠動態部署測試用例,并且對題庫題目及測試數據進行管理,筆者擴展了doctest對象模型,實現了測試數據遠程存儲和訪問管理。相關功能結構,如圖4所示。

圖4 測試數據的存儲和訪問管理
首先在遠程服務器端搭建了MySQL數據庫,建立了TestBank表,用于存儲題庫數據,字段ID、Topic、DocStr和Answer分別表示題目編號、題目描述、測試用例和標準答案;另外在服務器端設立了基于Python Flask框架的Web服務,提供了Add、Delete、Update和Get等接口,分別用于向數據庫添加、刪除、更新和獲取題目數據;最后在doctest中增加了WebTestFinder和WebTestManager兩個類,WebTestFinder用于替換DocTestFinder,其功能為根據題目編號獲取遠程的測試用例,WebTestManage則根據題目編號對遠程題庫中的數據進行增加、刪除和修改等維護性操作。
學生程序評測時,doctest通過擴展后的WebTestFinder自動從遠程數據庫中獲取測試用例,實現了測試用例的動態部署。
為了給測試用例增加權重設定項,主要在以下三個方面對doctest做出修改。
(1) 存儲測試用例權重。實現思路為擴展測試用例父類Example建立子類MyExample,子類中增加Weight屬性,用于存儲每個測試用例實例的權重。
(2) 修改測試用例格式。原測試用例由兩部分組成:Python語句和輸出結果。為了能夠描述測試用例權重,筆者約定,在Python語句之前若出現#Weight=N形式的行,則N表示此測試用例的權重。如下方示例,該測試用例的權重為3。
#Weight=3
>>>factorial(10)
3 628 800
(3) 分析識別測試用例權重。實現思路為擴展父類DocTestParser建立子類MyDocTestParser,子類中重寫了識別測試用例的parse方法,其中用于識別測試用例的正則表達式從二元組(語句-結果)修改為三元組(權重-語句-結果)。
將doctest的評測輸出結果由文本格式改為HTML格式,其思路如下。
(1) 擴展父類DocTestRunner建立子類MyDocTestRunner,子類中設立Results列表,存儲測試結果數據,每個Result包含測試用例、實際運行結果和是否通過測試三個屬性;
(2) 子類中重寫summarize方法,功能為循環遍歷Results列表,按照HTML表格(Table)的語法格式總結輸出評測結果。表格中每一行展示一個測試用例的評測信息,包括編號、Python語句、正確結果、實際結果、權重和是否通過測試六列數據。
仍以階乘函數factorial(n)為例,按本文方法對doctest擴展后,測試用例采用后期動態部署方式進行設置,如圖5所示。

圖5 動態部署測試用例
學生在編寫程序和提交程序時看不到測試用例,系統接收到學生程序后,按照題目編號搜索與之匹配的測試用例,將兩者組合后再進行測試。
這里,測試用例采用三元組的形式,加入了權重項。改進后的評測結果改為HTML格式,便于查看和數據統計,如圖6所示。

圖6 HTML格式的評測結果
Python標準庫中的doctest模塊通過文檔字符串中的測試用例實現程序的自動化測試,測試功能較完備,內部編程模型和評測思路對于教學實踐環節中Python程序自動評判方法的研究具有較高的參考價值和指導意義。本文深入研究了doctest的內部對象模型,通過實例討論了doctest的測試步驟,分析和總結了doctest的缺點與不足,并針對這些不足之處提出了相應的改進方案,通過擴展doctest編程模型實現了測試用例的動態部署、測試用例權重設定和評測結果的格式化輸出。
2019—2020學年第1學期,在我校19級經濟和會計兩個專業(6個班,180名學生)的Python程序設計課程中,應用了以本文方法為核心設計實現的Python程序自動評測系統,對上機實踐環節學生編程作業進行全自動評測,結果顯示該系統能夠靈活管理和部署測試用例,評測準確、效率高,長期運行穩定,滿足了較大規模場景下教學和考試的需要。