亓雪冬
(中国石油大学(华东),信息化建设处,山东,青岛 266580)
程序设计类课程是大学通识类教育课程中的重要组成部分,课程特点是理论与实践紧密结合,上机编程练习对理解和消化课程内容非常关键,同时为了更好地检验学生的实际编程能力,程序设计类课程的考试环节中采用上机考试形式已成为趋势[1]。上机练习或考试均需要以程序自动评测系统作为支撑,目前常见的系统有Moodle和OJ。Moodle是一个课程管理系统,最初由澳大利亚教师Martin Dougiamas开发,初期不具有程序评测功能,后作为一个插件加入[2-3]。OJ是Online Judge系统的统称,主要用于程序竞赛时对程序进行评测,国内比较有影响力的主要有北京大学和哈尔滨工业大学的OJ系统[4]。
Moodle和OJ的程序评测原理基本类似,通过操作系统的输入输出重定向捕获控制台程序(也被称为命令行程序)的输入输出,适合于对C、C++和数据结构等程序算法类课程进行自动评测[5-7]。然而在C#程序设计课程中,程序的表现形式主要以GUI(Graphic User Interface,图形用户界面)程序为主,Moodle和OJ均不能对该类程序进行评测。此外Moodle和OJ均采用服务器集中评测架构,易导致服务器负载过重,无法支撑大规模(1 000人以上)用户同时使用。本研究以C#程序的自动评测为背景,研究了可支持大规模用户的分散式程序评测架构,分析GUI程序与控制台程序的区别,提出针对GUI程序的管控模式评测方案,并讨论评测过程中的关键技术细节。
在程序评测系统的架构设计上,Moodle和OJ都采用服务器集中评测架构,如图1(a)所示。这种架构中,服务器通常由Web服务器、数据库服务器和程序评测服务器等部件组成,客户端(学生机端)仅需安装浏览器。这种架构的优点是客户端部署简便,无须安装专用的软件,缺点是学生端仅充当程序的编写工具,而所有学生程序的编译和评测均需在服务器端完成,服务器端负载压力聚集易出现资源瓶颈。
(a)服务器端集中式评测架构
为了分散负载压力,支持大规模用户量下的程序评测,调整了学生机和服务器的负载分配,提出了学生端分散式评测架构,如图1(b)所示。新的架构中,服务器端程序评测服务器被移除,程序的编写、编译和评测等工作均在学生机中完成,仅需将评测结果发布到服务器保存,这使得所有学生机均分了原服务器中的程序评测工作量,分散了负载压力,解除了资源瓶颈,提高了系统用户承载规模。
控制台程序的特点是使用标准输入输出流与操作系统进行数据交互。借助这个特点,使用操作系统输入输出重定向功能,将输入输出流对接到外部文件,在不干扰程序正常运行的情况下,对比程序实际输出和预设输出,即可完成程序评测。
GUI程序具有图形用户界面,不使用标准输入输出流而是通过界面元素(如文本框、按钮等)与外界进行数据交互,采用事件机制驱动程序执行,与控制台程序有本质区别,因此原控制台程序的评测方案对GUI程序并不适用。
在深入分析GUI程序运行特点基础上,提出基于管控模式的程序评测方案,如图2所示。这种方案中,管控模块和待评测的GUI窗体模块组合为单一程序,管控模块是自动评测的核心,管理和控制整个评测过程。评测过程由5个步骤组成:①程序启动后首先执行管控模块,再由管控模块引导启动待评测的GUI窗体;②管控模块根据评测用例将输入数据填充到控件中;③管控模块执行控件的事件委托,实际效果相当于触发控件相关事件;④管控模块读取控件输出数据并与预设的触发事件后的结果数据进行比较,输出评测结果;⑤管控模块关闭GUI窗体并结束自身程序,评测完成。
图2 基于管控模式的程序评测方案
C#程序设计课程中,一个基本的GUI程序包含Form1.cs、Form1.Designer.cs和Program.cs等3个文件,其中,Form1.cs和Form1.Designer.cs为窗体模块,Form1.cs包含窗体的功能逻辑代码,Form1.Designer.cs包含窗体的布局代码,Program.cs主要包含Main函数用于启动Form1窗体。与学生相关的代码全部集中在Form1.cs和Form1.Designer.cs中,与Program.cs无关。因此将管控模块置入Program.cs中。编译时,管控模块与窗体模块组合在一个程序中;运行时,管控模块管理和控制窗体模块的评测过程。
根据评测方案,管控模块应包含启动和关闭窗体模块、执行评测处理以及窗体模块运行超时时强制关闭程序等功能。管控模块内部逻辑核心代码如下。
01 System.Threading.Timer timer;//定义定时器变量
02 TestRule[]testRules;//定义存储测试规则的数组
03 void Main(){
04 timer = new System.Threading.Timer(OnTimeOut,null,5 000,-1);
05 Form1 testForm = new Form1();//创建窗体对象
06 testRules = ReadTestRules();//读取测试规则
07 foreach(TestRule rule in testRules)//循环处理每一个测试规则
08 TestForm(testForm, rule);
09 OutputResult(testRules);//输出测试结果
10 Form1.Close();//关闭窗体对象
11 }
12 void OnTimeOut(object state){//定时器处理程序
13 Environment.Exit(1);//强制结束程序
14 }
①第5行和第10行中,管控模块在评测前和评测后分别启动和关闭窗体模块。②第4行设置了定时器timer,用于对窗体模块进行超时检测,避免学生程序进入死循环干扰评测过程。此处设置时间阈值为5 s(5 000 ms),超时后管控模块调用第12行的OnTimeOut函数,强制结束程序。③第6行读取测试用例集,每个测试用例包含输入数据、触发的事件、输出数据、评测结果等数据项;第7、8行循环使用每一个测试用例对窗体进行评测;第9行将捕获的测试结果输出。
C#语言GUI程序通过界面控件(如文本框、按钮等)与外界进行数据交互。为了使评测过程自动化,管控模块需要能够按照评测规则读取和写入控件数据。然而管控模块在窗体模块外部,不能直接读写窗体模块内部的控件数据,需要借助C#反射功能实现。
以文本框控件textBox1为例,读写该控件数据主要包括以下2个步骤。
(1)管控模块通过反射取得窗体模块textBox1的引用。这里,Form1为窗体模块名,textBox1为控件名。因为窗体内的控件均为窗体的私有变量,因此需要NonPublic和Instance样式的绑定说明。
TextBox textBox1 =(TextBox)Form1.GetType()
.GetField("textBox1", BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(Form1);
(2)通过赋值语句读写控件数据
textBox1.Text = 写入的数据
读取的数据 = textBox1.Text
GUI程序依赖事件机制驱动程序执行,C#语言中触发事件时会自动调用该事件对应的委托函数。为了模拟触发事件的行为,管控模块通过C#反射功能直接提取事件对应的委托函数并执行。
以按钮控件button1的鼠标单击事件为例,事件委托函数原型为
void button1_Click(object sender, EventArgs e)
模拟触发该事件的过程主要包括以下3个步骤。
(1)获取button1所有事件委托函数列表,这其中包含了鼠标单击事件的委托函数。
EventHandlerList events =(EventHandlerList)(typeof(Button)
.GetProperty("Events", BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(button1, null));
(2)在上述委托函数列表中,以鼠标单击事件名“EventClick”作为键值,得到鼠标单击事件的委托函数。处理后,handler变量即表示button1_Click委托函数。
object key = typeof(Control)
.GetField("EventClick", BindingFlags.NonPublic|BindingFlags.Static)
.GetValue(null);
Delegate handler = events[key];
(3)调用委托函数,模拟触发鼠标单击事件。
handler.DynamicInvoke(button1, EventArgs.Empty);
评测系统在应用过程中出现的问题及后续改进思路总结如下。
(1)可评测控件种类不够丰富。目前可评测的控件包括标签Label、文本框TextBox、单选按钮RadioButton、复选框CheckBox、组合框ComboBox、列表框ListBox和进度条ProgressBar等常用控件。下一步需要加入菜单MenuStrip、列表视图ListView和树形视图TreeView等功能更复杂的控件,进一步丰富题目类型。
(2)学生编写的程序与测评的程序不一致。部分学生在评测前未对最新修改的程序进行保存,导致评测结果与学生程序不匹配。下一步对评测系统进行改进,在评测前显示提示信息,提醒学生对修改后的程序进行保存。
(3)编写评测规则较繁琐、效率低。评测规则采用Json格式,每一道题目的评测规则由多个子规则构成,每个子规则包含输入数据、触发的事件和输出数据。目前评测规则由教师手工建立,繁琐且效率低。下一步拟增加评测规则自动生成模块,教师对题目进行测试时自动捕获测试数据用以生成评测规则,提高题目维护效率。
评测系统采用学生端分散式评测架构,充分利用机房中每一台学生机的计算能力,分散了负载压力,提高了系统可承载用户规模,满足了大规模上机考试的需要。在2018—2019学年第1学期,该系统仅使用一台虚拟服务器(4个2.2GHz CPU、4G内存)承担全校程序设计上机考试,共3场考试,第1场共800人,第2场和第3场每场均为1 500人,总计3 800人。考试过程中,系统运行稳定,发题、评测和回收答案未出现任何异常,考试顺利完成。系统运行至今,累计超过10 000名学生使用该系统进行程序设计的自测和考试,累计评测题目数百万个。共承担程序设计相关课程阶段性测试和期末上机考试任务数十场次,系统效果好,利用率高,具有示范性和辐射性。