亓雪冬, 葛元康
(中國(guó)石油大學(xué)(華東) 1.信息化建設(shè)處; 2.計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院, 山東 青島 266580)
近年來,Python語言以其簡(jiǎn)潔的語法、靈活的交互性與動(dòng)態(tài)性和豐富的第三方程序庫(kù)等特點(diǎn)快速被國(guó)內(nèi)外高校所認(rèn)可。嵩天等提出,Python語言是程序設(shè)計(jì)課程教學(xué)改革的理想選擇[1]。眾多高校中開設(shè)了大量以Python語言為載體的程序設(shè)計(jì)類課程,以我校為例,2019—2020學(xué)年約12個(gè)專業(yè)(20多個(gè)班級(jí))在全校公共基礎(chǔ)課《程序設(shè)計(jì)》課程中,將授課編程語言改變?yōu)镻ython。新的選擇給教學(xué)工作帶來機(jī)遇的同時(shí)也帶來了挑戰(zhàn)。
程序自動(dòng)評(píng)測(cè)系統(tǒng)能夠快速對(duì)學(xué)生編寫的程序進(jìn)行自動(dòng)評(píng)判[2-3],實(shí)現(xiàn)了批改和反饋的自動(dòng)化[4-5],是程序設(shè)計(jì)類課程教學(xué)實(shí)踐環(huán)節(jié)不可或缺的教學(xué)工具,是提高學(xué)習(xí)效率、保障教學(xué)效果和提升教學(xué)質(zhì)量的重要技術(shù)手段。因此,研究Python程序自動(dòng)評(píng)判方法對(duì)于教學(xué)改革具有重要的現(xiàn)實(shí)意義。本文在充分研究Python標(biāo)準(zhǔn)庫(kù)原有測(cè)試模塊doctest的基礎(chǔ)上,根據(jù)教學(xué)實(shí)踐環(huán)節(jié)的實(shí)際需求,提出了擴(kuò)展和改造doctest編程模型的思路和方法,實(shí)現(xiàn)了Python程序的全自動(dòng)評(píng)判。
doctest是Python標(biāo)準(zhǔn)庫(kù)中的自動(dòng)化測(cè)試模塊,根據(jù)編寫在文檔字符串中的測(cè)試用例對(duì)待測(cè)試模塊的功能進(jìn)行測(cè)試。文檔字符串(DocStrings)是Python模塊、類或函數(shù)定義中的起始語句,可看作多行注釋,定界符為三個(gè)單引號(hào)(' ' ')或者三個(gè)雙引號(hào)(""")。將測(cè)試用例放入文檔字符串中,易于查看和修改。測(cè)試用例格式與Python交互式環(huán)境中執(zhí)行語句的格式完全一致。
doctest的核心測(cè)試功能由2個(gè)容器類和4個(gè)處理類提供。對(duì)象模型[6],如圖1所示。
圖1 doctest對(duì)象模型
兩個(gè)容器類分別為Example和DocTest,用于存儲(chǔ)測(cè)試文本中提取的測(cè)試用例。其中,Example表示單個(gè)測(cè)試用列,由一條Python語句和其對(duì)應(yīng)的輸出組合而成;DocTest表示Example(測(cè)試用例)的集合。
四個(gè)處理類分別為DocTestFinder、DocTestParser、DocTestRunner和OutputChecker。其中,DocTestFinder用于發(fā)現(xiàn)測(cè)試模塊中的文檔字符串;DocTestParser用于分析并返回文檔字符串中的測(cè)試用例集合DocTest;DocTestRunner控制執(zhí)行DocTest中每一個(gè)測(cè)試用例,同時(shí)調(diào)用OutputChecker檢查用例的輸出,最后統(tǒng)計(jì)并輸出測(cè)試結(jié)論。
(1) 描述測(cè)試用例
例如對(duì)于求取n階乘的函數(shù)factorial(n),設(shè)計(jì)測(cè)試用例對(duì)其進(jìn)行測(cè)試。程序結(jié)構(gòu),如圖2所示。
圖2 包含doctest測(cè)試用例的程序結(jié)構(gòu)
程序上部的文檔字符串描述了對(duì)factorial(n)進(jìn)行測(cè)試的測(cè)試用例,程序下部為factorial函數(shù)的定義。
測(cè)試用例的設(shè)計(jì)上,根據(jù)階乘定義factorial(n)=1*2*…*n,可選取n≥1的正整數(shù)作為測(cè)試用例,如n=10時(shí),階乘為3 628 800。另外對(duì)于特殊情況n=0時(shí),階乘為1,這一點(diǎn)經(jīng)常有同學(xué)出錯(cuò),這個(gè)特例也可作為一個(gè)測(cè)試用例。每個(gè)測(cè)試用例中,三個(gè)右箭頭(>>>)起始的行表示待執(zhí)行的Python語句,隨后的行表示期待的正確輸出結(jié)果,格式與在IDLE或Python Shell中執(zhí)行Python語句類似。
(2) 執(zhí)行測(cè)試
上述程序代碼存儲(chǔ)在文件example.py中。使用doctest對(duì)其執(zhí)行測(cè)試的命令如下。
import doctest, example
doctest.testmod(example)
第1句導(dǎo)入測(cè)試工具doctest和用戶程序example.py。example中不僅包含了階乘函數(shù)的定義,而且包含了對(duì)其進(jìn)行測(cè)試的兩個(gè)測(cè)試用例。第2句調(diào)用doctest.testmod函數(shù)對(duì)example模塊進(jìn)行測(cè)試。測(cè)試過程如1.1節(jié)描述,包含發(fā)現(xiàn)和分析測(cè)試用例,執(zhí)行測(cè)試用例,返回測(cè)試結(jié)果等步驟。
(3) 返回測(cè)試結(jié)果
假設(shè)編寫的factorial函數(shù)沒有考慮0的階乘這種特殊情況,第1個(gè)測(cè)試通過,第2個(gè)測(cè)試失敗。返回的測(cè)試結(jié)果,如圖3所示。
圖3 測(cè)試結(jié)果
測(cè)試結(jié)果整體分為兩部分,上部為對(duì)每一個(gè)測(cè)試用例的詳細(xì)描述,如果測(cè)試失敗,還會(huì)給出程序?qū)嶋H輸出的錯(cuò)誤結(jié)果以及期待的正確結(jié)果;下部為整體測(cè)試結(jié)論,包含測(cè)試數(shù)目以及測(cè)試失敗的數(shù)目。
doctest雖具備較完整的程序測(cè)試功能,但程序測(cè)試與教師對(duì)學(xué)生程序的評(píng)測(cè)這兩者概念不完全相同,將doctest應(yīng)用在Python課程的作業(yè)和考試評(píng)測(cè)環(huán)節(jié)中時(shí),仍存在一些不足,主要體現(xiàn)在以下三個(gè)方面。
(1) 測(cè)試用例的部署。doctest中的測(cè)試用例與被測(cè)試代碼在同一個(gè)文件中。如果在學(xué)生編寫程序的文件中提前設(shè)置好測(cè)試用例,由于學(xué)生可以看到測(cè)試用例,會(huì)降低考查的難度甚至失去考查的意義。因此需要在獲得學(xué)生提交的程序之后,動(dòng)態(tài)部署教師設(shè)置的測(cè)試用例,然后再進(jìn)行評(píng)測(cè)。
(2) 測(cè)試用例無權(quán)重設(shè)定。doctest測(cè)試用例中沒有權(quán)重屬性,這將無法區(qū)分不同測(cè)試用例的重要程度。然而在教學(xué)實(shí)踐中,教師往往會(huì)根據(jù)授課內(nèi)容的先后順序、難易程度和課程目標(biāo)等,調(diào)整不同知識(shí)點(diǎn)對(duì)應(yīng)的分值。對(duì)應(yīng)到程序評(píng)測(cè)中,則需要能夠?qū)^重要的測(cè)試用例設(shè)置較高的權(quán)重,而對(duì)相對(duì)次要的測(cè)試用例設(shè)置較低的權(quán)重。
(3) 評(píng)測(cè)結(jié)果難于理解。doctest的測(cè)試結(jié)果缺乏測(cè)試用例的編號(hào)項(xiàng),基于文本的輸出形式布局過于簡(jiǎn)潔,對(duì)初學(xué)程序的學(xué)生來說難于理解。教學(xué)實(shí)踐中,需將評(píng)測(cè)結(jié)果修改為HTML格式,每行描述一個(gè)測(cè)試用例,方便學(xué)生查看,并易于和其它Web教學(xué)系統(tǒng)結(jié)合。
針對(duì)以上三點(diǎn)不足,筆者嘗試通過擴(kuò)展doctest對(duì)象模型加以解決。通過擴(kuò)展DocTestFinder類實(shí)現(xiàn)測(cè)試用例的動(dòng)態(tài)部署;通過擴(kuò)展Example類和DocTestParser類實(shí)現(xiàn)測(cè)試用例權(quán)重的設(shè)定;通過擴(kuò)展DocTestRunner類實(shí)現(xiàn)評(píng)測(cè)結(jié)果以HTML格式進(jìn)行輸出。
為了能夠動(dòng)態(tài)部署測(cè)試用例,并且對(duì)題庫(kù)題目及測(cè)試數(shù)據(jù)進(jìn)行管理,筆者擴(kuò)展了doctest對(duì)象模型,實(shí)現(xiàn)了測(cè)試數(shù)據(jù)遠(yuǎn)程存儲(chǔ)和訪問管理。相關(guān)功能結(jié)構(gòu),如圖4所示。
圖4 測(cè)試數(shù)據(jù)的存儲(chǔ)和訪問管理
首先在遠(yuǎn)程服務(wù)器端搭建了MySQL數(shù)據(jù)庫(kù),建立了TestBank表,用于存儲(chǔ)題庫(kù)數(shù)據(jù),字段ID、Topic、DocStr和Answer分別表示題目編號(hào)、題目描述、測(cè)試用例和標(biāo)準(zhǔn)答案;另外在服務(wù)器端設(shè)立了基于Python Flask框架的Web服務(wù),提供了Add、Delete、Update和Get等接口,分別用于向數(shù)據(jù)庫(kù)添加、刪除、更新和獲取題目數(shù)據(jù);最后在doctest中增加了WebTestFinder和WebTestManager兩個(gè)類,WebTestFinder用于替換DocTestFinder,其功能為根據(jù)題目編號(hào)獲取遠(yuǎn)程的測(cè)試用例,WebTestManage則根據(jù)題目編號(hào)對(duì)遠(yuǎn)程題庫(kù)中的數(shù)據(jù)進(jìn)行增加、刪除和修改等維護(hù)性操作。
學(xué)生程序評(píng)測(cè)時(shí),doctest通過擴(kuò)展后的WebTestFinder自動(dòng)從遠(yuǎn)程數(shù)據(jù)庫(kù)中獲取測(cè)試用例,實(shí)現(xiàn)了測(cè)試用例的動(dòng)態(tài)部署。
為了給測(cè)試用例增加權(quán)重設(shè)定項(xiàng),主要在以下三個(gè)方面對(duì)doctest做出修改。
(1) 存儲(chǔ)測(cè)試用例權(quán)重。實(shí)現(xiàn)思路為擴(kuò)展測(cè)試用例父類Example建立子類MyExample,子類中增加Weight屬性,用于存儲(chǔ)每個(gè)測(cè)試用例實(shí)例的權(quán)重。
(2) 修改測(cè)試用例格式。原測(cè)試用例由兩部分組成:Python語句和輸出結(jié)果。為了能夠描述測(cè)試用例權(quán)重,筆者約定,在Python語句之前若出現(xiàn)#Weight=N形式的行,則N表示此測(cè)試用例的權(quán)重。如下方示例,該測(cè)試用例的權(quán)重為3。
#Weight=3
>>>factorial(10)
3 628 800
(3) 分析識(shí)別測(cè)試用例權(quán)重。實(shí)現(xiàn)思路為擴(kuò)展父類DocTestParser建立子類MyDocTestParser,子類中重寫了識(shí)別測(cè)試用例的parse方法,其中用于識(shí)別測(cè)試用例的正則表達(dá)式從二元組(語句-結(jié)果)修改為三元組(權(quán)重-語句-結(jié)果)。
將doctest的評(píng)測(cè)輸出結(jié)果由文本格式改為HTML格式,其思路如下。
(1) 擴(kuò)展父類DocTestRunner建立子類MyDocTestRunner,子類中設(shè)立Results列表,存儲(chǔ)測(cè)試結(jié)果數(shù)據(jù),每個(gè)Result包含測(cè)試用例、實(shí)際運(yùn)行結(jié)果和是否通過測(cè)試三個(gè)屬性;
(2) 子類中重寫summarize方法,功能為循環(huán)遍歷Results列表,按照HTML表格(Table)的語法格式總結(jié)輸出評(píng)測(cè)結(jié)果。表格中每一行展示一個(gè)測(cè)試用例的評(píng)測(cè)信息,包括編號(hào)、Python語句、正確結(jié)果、實(shí)際結(jié)果、權(quán)重和是否通過測(cè)試六列數(shù)據(jù)。
仍以階乘函數(shù)factorial(n)為例,按本文方法對(duì)doctest擴(kuò)展后,測(cè)試用例采用后期動(dòng)態(tài)部署方式進(jìn)行設(shè)置,如圖5所示。
圖5 動(dòng)態(tài)部署測(cè)試用例
學(xué)生在編寫程序和提交程序時(shí)看不到測(cè)試用例,系統(tǒng)接收到學(xué)生程序后,按照題目編號(hào)搜索與之匹配的測(cè)試用例,將兩者組合后再進(jìn)行測(cè)試。
這里,測(cè)試用例采用三元組的形式,加入了權(quán)重項(xiàng)。改進(jìn)后的評(píng)測(cè)結(jié)果改為HTML格式,便于查看和數(shù)據(jù)統(tǒng)計(jì),如圖6所示。
圖6 HTML格式的評(píng)測(cè)結(jié)果
Python標(biāo)準(zhǔn)庫(kù)中的doctest模塊通過文檔字符串中的測(cè)試用例實(shí)現(xiàn)程序的自動(dòng)化測(cè)試,測(cè)試功能較完備,內(nèi)部編程模型和評(píng)測(cè)思路對(duì)于教學(xué)實(shí)踐環(huán)節(jié)中Python程序自動(dòng)評(píng)判方法的研究具有較高的參考價(jià)值和指導(dǎo)意義。本文深入研究了doctest的內(nèi)部對(duì)象模型,通過實(shí)例討論了doctest的測(cè)試步驟,分析和總結(jié)了doctest的缺點(diǎn)與不足,并針對(duì)這些不足之處提出了相應(yīng)的改進(jìn)方案,通過擴(kuò)展doctest編程模型實(shí)現(xiàn)了測(cè)試用例的動(dòng)態(tài)部署、測(cè)試用例權(quán)重設(shè)定和評(píng)測(cè)結(jié)果的格式化輸出。
2019—2020學(xué)年第1學(xué)期,在我校19級(jí)經(jīng)濟(jì)和會(huì)計(jì)兩個(gè)專業(yè)(6個(gè)班,180名學(xué)生)的Python程序設(shè)計(jì)課程中,應(yīng)用了以本文方法為核心設(shè)計(jì)實(shí)現(xiàn)的Python程序自動(dòng)評(píng)測(cè)系統(tǒng),對(duì)上機(jī)實(shí)踐環(huán)節(jié)學(xué)生編程作業(yè)進(jìn)行全自動(dòng)評(píng)測(cè),結(jié)果顯示該系統(tǒng)能夠靈活管理和部署測(cè)試用例,評(píng)測(cè)準(zhǔn)確、效率高,長(zhǎng)期運(yùn)行穩(wěn)定,滿足了較大規(guī)模場(chǎng)景下教學(xué)和考試的需要。