亓雪冬
(中國石油大學(xué)(華東),信息化建設(shè)處,山東,青島 266580)
程序設(shè)計類課程是大學(xué)通識類教育課程中的重要組成部分,課程特點是理論與實踐緊密結(jié)合,上機編程練習(xí)對理解和消化課程內(nèi)容非常關(guān)鍵,同時為了更好地檢驗學(xué)生的實際編程能力,程序設(shè)計類課程的考試環(huán)節(jié)中采用上機考試形式已成為趨勢[1]。上機練習(xí)或考試均需要以程序自動評測系統(tǒng)作為支撐,目前常見的系統(tǒng)有Moodle和OJ。Moodle是一個課程管理系統(tǒng),最初由澳大利亞教師Martin Dougiamas開發(fā),初期不具有程序評測功能,后作為一個插件加入[2-3]。OJ是Online Judge系統(tǒng)的統(tǒng)稱,主要用于程序競賽時對程序進行評測,國內(nèi)比較有影響力的主要有北京大學(xué)和哈爾濱工業(yè)大學(xué)的OJ系統(tǒng)[4]。
Moodle和OJ的程序評測原理基本類似,通過操作系統(tǒng)的輸入輸出重定向捕獲控制臺程序(也被稱為命令行程序)的輸入輸出,適合于對C、C++和數(shù)據(jù)結(jié)構(gòu)等程序算法類課程進行自動評測[5-7]。然而在C#程序設(shè)計課程中,程序的表現(xiàn)形式主要以GUI(Graphic User Interface,圖形用戶界面)程序為主,Moodle和OJ均不能對該類程序進行評測。此外Moodle和OJ均采用服務(wù)器集中評測架構(gòu),易導(dǎo)致服務(wù)器負載過重,無法支撐大規(guī)模(1 000人以上)用戶同時使用。本研究以C#程序的自動評測為背景,研究了可支持大規(guī)模用戶的分散式程序評測架構(gòu),分析GUI程序與控制臺程序的區(qū)別,提出針對GUI程序的管控模式評測方案,并討論評測過程中的關(guān)鍵技術(shù)細節(jié)。
在程序評測系統(tǒng)的架構(gòu)設(shè)計上,Moodle和OJ都采用服務(wù)器集中評測架構(gòu),如圖1(a)所示。這種架構(gòu)中,服務(wù)器通常由Web服務(wù)器、數(shù)據(jù)庫服務(wù)器和程序評測服務(wù)器等部件組成,客戶端(學(xué)生機端)僅需安裝瀏覽器。這種架構(gòu)的優(yōu)點是客戶端部署簡便,無須安裝專用的軟件,缺點是學(xué)生端僅充當(dāng)程序的編寫工具,而所有學(xué)生程序的編譯和評測均需在服務(wù)器端完成,服務(wù)器端負載壓力聚集易出現(xiàn)資源瓶頸。
(a)服務(wù)器端集中式評測架構(gòu)
為了分散負載壓力,支持大規(guī)模用戶量下的程序評測,調(diào)整了學(xué)生機和服務(wù)器的負載分配,提出了學(xué)生端分散式評測架構(gòu),如圖1(b)所示。新的架構(gòu)中,服務(wù)器端程序評測服務(wù)器被移除,程序的編寫、編譯和評測等工作均在學(xué)生機中完成,僅需將評測結(jié)果發(fā)布到服務(wù)器保存,這使得所有學(xué)生機均分了原服務(wù)器中的程序評測工作量,分散了負載壓力,解除了資源瓶頸,提高了系統(tǒng)用戶承載規(guī)模。
控制臺程序的特點是使用標(biāo)準(zhǔn)輸入輸出流與操作系統(tǒng)進行數(shù)據(jù)交互。借助這個特點,使用操作系統(tǒng)輸入輸出重定向功能,將輸入輸出流對接到外部文件,在不干擾程序正常運行的情況下,對比程序?qū)嶋H輸出和預(yù)設(shè)輸出,即可完成程序評測。
GUI程序具有圖形用戶界面,不使用標(biāo)準(zhǔn)輸入輸出流而是通過界面元素(如文本框、按鈕等)與外界進行數(shù)據(jù)交互,采用事件機制驅(qū)動程序執(zhí)行,與控制臺程序有本質(zhì)區(qū)別,因此原控制臺程序的評測方案對GUI程序并不適用。
在深入分析GUI程序運行特點基礎(chǔ)上,提出基于管控模式的程序評測方案,如圖2所示。這種方案中,管控模塊和待評測的GUI窗體模塊組合為單一程序,管控模塊是自動評測的核心,管理和控制整個評測過程。評測過程由5個步驟組成:①程序啟動后首先執(zhí)行管控模塊,再由管控模塊引導(dǎo)啟動待評測的GUI窗體;②管控模塊根據(jù)評測用例將輸入數(shù)據(jù)填充到控件中;③管控模塊執(zhí)行控件的事件委托,實際效果相當(dāng)于觸發(fā)控件相關(guān)事件;④管控模塊讀取控件輸出數(shù)據(jù)并與預(yù)設(shè)的觸發(fā)事件后的結(jié)果數(shù)據(jù)進行比較,輸出評測結(jié)果;⑤管控模塊關(guān)閉GUI窗體并結(jié)束自身程序,評測完成。
圖2 基于管控模式的程序評測方案
C#程序設(shè)計課程中,一個基本的GUI程序包含F(xiàn)orm1.cs、Form1.Designer.cs和Program.cs等3個文件,其中,Form1.cs和Form1.Designer.cs為窗體模塊,F(xiàn)orm1.cs包含窗體的功能邏輯代碼,F(xiàn)orm1.Designer.cs包含窗體的布局代碼,Program.cs主要包含Main函數(shù)用于啟動Form1窗體。與學(xué)生相關(guān)的代碼全部集中在Form1.cs和Form1.Designer.cs中,與Program.cs無關(guān)。因此將管控模塊置入Program.cs中。編譯時,管控模塊與窗體模塊組合在一個程序中;運行時,管控模塊管理和控制窗體模塊的評測過程。
根據(jù)評測方案,管控模塊應(yīng)包含啟動和關(guān)閉窗體模塊、執(zhí)行評測處理以及窗體模塊運行超時時強制關(guān)閉程序等功能。管控模塊內(nèi)部邏輯核心代碼如下。
01 System.Threading.Timer timer;//定義定時器變量
02 TestRule[]testRules;//定義存儲測試規(guī)則的數(shù)組
03 void Main(){
04 timer = new System.Threading.Timer(OnTimeOut,null,5 000,-1);
05 Form1 testForm = new Form1();//創(chuàng)建窗體對象
06 testRules = ReadTestRules();//讀取測試規(guī)則
07 foreach(TestRule rule in testRules)//循環(huán)處理每一個測試規(guī)則
08 TestForm(testForm, rule);
09 OutputResult(testRules);//輸出測試結(jié)果
10 Form1.Close();//關(guān)閉窗體對象
11 }
12 void OnTimeOut(object state){//定時器處理程序
13 Environment.Exit(1);//強制結(jié)束程序
14 }
①第5行和第10行中,管控模塊在評測前和評測后分別啟動和關(guān)閉窗體模塊。②第4行設(shè)置了定時器timer,用于對窗體模塊進行超時檢測,避免學(xué)生程序進入死循環(huán)干擾評測過程。此處設(shè)置時間閾值為5 s(5 000 ms),超時后管控模塊調(diào)用第12行的OnTimeOut函數(shù),強制結(jié)束程序。③第6行讀取測試用例集,每個測試用例包含輸入數(shù)據(jù)、觸發(fā)的事件、輸出數(shù)據(jù)、評測結(jié)果等數(shù)據(jù)項;第7、8行循環(huán)使用每一個測試用例對窗體進行評測;第9行將捕獲的測試結(jié)果輸出。
C#語言GUI程序通過界面控件(如文本框、按鈕等)與外界進行數(shù)據(jù)交互。為了使評測過程自動化,管控模塊需要能夠按照評測規(guī)則讀取和寫入控件數(shù)據(jù)。然而管控模塊在窗體模塊外部,不能直接讀寫窗體模塊內(nèi)部的控件數(shù)據(jù),需要借助C#反射功能實現(xiàn)。
以文本框控件textBox1為例,讀寫該控件數(shù)據(jù)主要包括以下2個步驟。
(1)管控模塊通過反射取得窗體模塊textBox1的引用。這里,F(xiàn)orm1為窗體模塊名,textBox1為控件名。因為窗體內(nèi)的控件均為窗體的私有變量,因此需要NonPublic和Instance樣式的綁定說明。
TextBox textBox1 =(TextBox)Form1.GetType()
.GetField("textBox1", BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(Form1);
(2)通過賦值語句讀寫控件數(shù)據(jù)
textBox1.Text = 寫入的數(shù)據(jù)
讀取的數(shù)據(jù) = textBox1.Text
GUI程序依賴事件機制驅(qū)動程序執(zhí)行,C#語言中觸發(fā)事件時會自動調(diào)用該事件對應(yīng)的委托函數(shù)。為了模擬觸發(fā)事件的行為,管控模塊通過C#反射功能直接提取事件對應(yīng)的委托函數(shù)并執(zhí)行。
以按鈕控件button1的鼠標(biāo)單擊事件為例,事件委托函數(shù)原型為
void button1_Click(object sender, EventArgs e)
模擬觸發(fā)該事件的過程主要包括以下3個步驟。
(1)獲取button1所有事件委托函數(shù)列表,這其中包含了鼠標(biāo)單擊事件的委托函數(shù)。
EventHandlerList events =(EventHandlerList)(typeof(Button)
.GetProperty("Events", BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(button1, null));
(2)在上述委托函數(shù)列表中,以鼠標(biāo)單擊事件名“EventClick”作為鍵值,得到鼠標(biāo)單擊事件的委托函數(shù)。處理后,handler變量即表示button1_Click委托函數(shù)。
object key = typeof(Control)
.GetField("EventClick", BindingFlags.NonPublic|BindingFlags.Static)
.GetValue(null);
Delegate handler = events[key];
(3)調(diào)用委托函數(shù),模擬觸發(fā)鼠標(biāo)單擊事件。
handler.DynamicInvoke(button1, EventArgs.Empty);
評測系統(tǒng)在應(yīng)用過程中出現(xiàn)的問題及后續(xù)改進思路總結(jié)如下。
(1)可評測控件種類不夠豐富。目前可評測的控件包括標(biāo)簽Label、文本框TextBox、單選按鈕RadioButton、復(fù)選框CheckBox、組合框ComboBox、列表框ListBox和進度條ProgressBar等常用控件。下一步需要加入菜單MenuStrip、列表視圖ListView和樹形視圖TreeView等功能更復(fù)雜的控件,進一步豐富題目類型。
(2)學(xué)生編寫的程序與測評的程序不一致。部分學(xué)生在評測前未對最新修改的程序進行保存,導(dǎo)致評測結(jié)果與學(xué)生程序不匹配。下一步對評測系統(tǒng)進行改進,在評測前顯示提示信息,提醒學(xué)生對修改后的程序進行保存。
(3)編寫評測規(guī)則較繁瑣、效率低。評測規(guī)則采用Json格式,每一道題目的評測規(guī)則由多個子規(guī)則構(gòu)成,每個子規(guī)則包含輸入數(shù)據(jù)、觸發(fā)的事件和輸出數(shù)據(jù)。目前評測規(guī)則由教師手工建立,繁瑣且效率低。下一步擬增加評測規(guī)則自動生成模塊,教師對題目進行測試時自動捕獲測試數(shù)據(jù)用以生成評測規(guī)則,提高題目維護效率。
評測系統(tǒng)采用學(xué)生端分散式評測架構(gòu),充分利用機房中每一臺學(xué)生機的計算能力,分散了負載壓力,提高了系統(tǒng)可承載用戶規(guī)模,滿足了大規(guī)模上機考試的需要。在2018—2019學(xué)年第1學(xué)期,該系統(tǒng)僅使用一臺虛擬服務(wù)器(4個2.2GHz CPU、4G內(nèi)存)承擔(dān)全校程序設(shè)計上機考試,共3場考試,第1場共800人,第2場和第3場每場均為1 500人,總計3 800人。考試過程中,系統(tǒng)運行穩(wěn)定,發(fā)題、評測和回收答案未出現(xiàn)任何異常,考試順利完成。系統(tǒng)運行至今,累計超過10 000名學(xué)生使用該系統(tǒng)進行程序設(shè)計的自測和考試,累計評測題目數(shù)百萬個。共承擔(dān)程序設(shè)計相關(guān)課程階段性測試和期末上機考試任務(wù)數(shù)十場次,系統(tǒng)效果好,利用率高,具有示范性和輻射性。