基于码云开展程序设计教学的自动判分方法和代码框架?

1. 目的

  • 学生基于码云提交程序设计作业;
  • 教师编写检查器,自动对所有人的作业进行判分;

2. 方法

下文中的代码用于说明方法思想,但存在少量 Bug,懒得同步更新了。查找最新代码,请移步码云【链接】。

2.1 作业提交要求

  1. 教师建立一个空项目,学生在该项目建立自己的分支(分支名 SEXXX,XXX 为学号后三位),fork该项目到本地;
    • 引用自集美大学计算机郑如滨的教学博客:『老师布置程序项目类作业的时候,即使再三强调要按照规范来建立项目目录结构。然而最终提交结果依然不尽如人意。每个人似乎都有自己的一套项目结构,并且这个结构通常是惨不忍睹的,比如一包流,所有代码均放到一个包中。教师完全可以规划好一个项目的标准目录结构,然后让学生Fork或clone下来,这样就无痛的规范了所有学生的项目目录结构。实际上这也是业界流行的一种方式。学生可以参考集美大学-郑如滨 老师的这个专门用于Java教学的一个项目OnlineShop,该项目包含一个可供参考的标准项目目录结构。』
  2. 学生将完成的程序设计作业 push 到远端自己对应的分支;
  3. 学生完成的程序作业要求满足一定的接口规范:如指定的文件名、函数名、输出输入接口等。

2.2 班级作业检查框架: CheckFramework.sh

  • 只需要学生的分支命名规则不变,每次作业的 CheckFramework不需要修改
  1. 思路

  2. 代码

#!/bin/sh
# Filename: CheckFramework.sh
#
# Usage checkRepo.sh git_repo_url
#
# Algorithm:
#   1.  clone git_repo to local directory
#   2.  checkout to each branch (of every student)
#   3.  testing and evaluating each program written by students.
#   4.  return result  (csv 格式)
#
# Input: git_repo_url
# output: result.csv   
#
######################################################################       

# 0. Global variants
ResultFile="result.csv" ;          # 返回每个学生的成绩
WorkSpace=`pwd`;
export WorkSpace;                      # 输出当前工作路径:WorkSpace

# 1. check repo_url
if [ $# -lt 1 ]; then
  echo "Usage:";
  echo "33[1;31m $0 33[m33[1;32m gitRepo_url33[m.";
  echo "Such as:";
  echo "33[1;31m $033[m33[1;32m  https://gitee.com/se16/HelloWorld.git 33[m";
  exit;
fi

# 2. git clone repository $1
# 2.0 if the repository have been cloned before, remove (delete) the repository and re-clone it.
RepoName=`echo ${1##*/} | cut -d "." -f 1`;
echo "the Repository name is 33[1;31m$RepoName33[m.";

if [ -d $RepoName ]; then
    echo "deleting the directory named $RepoName";
    rm -rf $RepoName;   # debug #
fi
# clone the Repository.
echo -e "Cloning the Repository: 33[1;31m$133[m.";
git clone $1;    # 第一次

# 2.1 check if the repository($2) was correctly cloned.

if [ ! -d $RepoName ]; then
    # incorrect repository!
    echo "Cloning repository ${1##*/} 33[1;31mfailed33[m. Check it's url, please!";
    exit;
fi
    
# 2.2 successfully cloned the repository. Now change working directory to sub-directory named $RepoName
#     step1 : checkout to $bra, 
#     step2 : and check the homeworks one by one, give evaluation score of each homework.
#             (use an function outsides) 
#     step3 : output result.

# 2.2.0 delete previous result.csv
if [[ -f $ResultFile ]]; then
    #statements 
    rm result.csv;
fi

echo -e "Processing homeworks in each branch named SEXXX";

cd $WorkSpace/$RepoName;

# 列举本地分支,筛选学生建立的『SEXXX』分支,删除当前分支前的星号 "*"" ,及空格
git branch --all | cut -d "/" -f 3| grep -ai 'se' | sed 's/[* ]//g'> $WorkSpace/branches.txt;

count=0;

while read bra; do
    let count=count+1;
    # 2.2.1 checkout ; OK
    git checkout -q $bra;  #  -q --quiet          # suppress progress reporting

    # 2.2.2 run test script, and return a score of current branch(student). Dev
    echo "
-e NO.33[1;31m$count33[m. Reviewing homeworks of student NO. 33[1;32m$bra ...33[m ";

    # call CheckersCaller : driver , and export TotalScore
        # echo $WorkSpace;
        source $WorkSpace/CheckersCaller.sh;
 
       # return score of current branch (student's work)
        score=$TotalScore;
      
        # 2.2.3 write score to a structure which records all students scores. OK
    echo "$bra,$score" >> $WorkSpace/$ResultFile
done < $WorkSpace/branches.txt

echo "
Finished";

2.3 作业的检查器: CheckersCaller.sh

  • 每次作业的 Checkers 不一样,需要独立设计
  • 对应地 CheckersCaller.sh 需要对 score 的汇总做微调 (包括:权重、汇总项目)
  1. 思路
  • 依次采用 命令 source Checker_i.sh ,调用不同的检查器;
  • 这些检查器 Checker_i.sh 输出 对应的检查项目得分 score_i
  • 对每一项检查赋予权重 wi, 对所有检查项目得分加权(wi),得到汇总得分 TotalScore
  • 输出TotalScore。(export TotalScore
  1. 代码
#!/bin/sh
# Filename: CheckersCaller.sh
#
# Usage: CheckersCaller.sh
#
# Algorithm:
# 1.    call checkers which export a set of scores
# 2.   summation:sum the scores to TotalScore
# 3.   export TotalScore
#
# Input:Null
# Output:TotalScore   -- summation of each score_i.
#
#####################################################       

TotalScore=0

# 1. 调用一组Checker_i.sh 脚本, 得到一组输出 score_i (单项得分)
source $WorkSpace/Checker_1.sh  "*.c";
let s1=score_1;

source $WorkSpace/Checker_1.sh  "*.cpp";
let s2=score_1;

source $WorkSpace/Checker_1.sh  "*.py";
let s3=score_1;

# ...
# source Checker_n.sh

# 2. 汇总得分 (wi 为每一项的权重)
# let TotalScore=score_1*w1+score_2*w2+...+score_n*wn
let TotalScore=s1+s2+s3;

echo "TotalScore : $TotalScore";

# 3. 输出汇总分 TotalScore
export TotalScore;

2.4 作业的单项检查器: Checker_i.sh

  1. 思路

  2. 代码示例

  • 这里给出一个静态代码审查的示例。(Checker_1.sh)
#!/bin/sh
# Checker_1.sh
#
# Usage Checker_1.sh filename
#
# Algorithm :
# 1.  check the file (SUT) existence
# 2.  Review or test SUT, give an evaluation score (score)
# 3.  export score_i  (score_i equals to evaluation result.)
#
# Input: filename  -- software under test(review)SUT
# Output:score_i   -- export score_i
#
#################################################       
# 0. Global variable for export
score_1=0                   # here i=1

# 1. checker_i Usage
if [ $# -lt 1 ]; then
  echo "Usage:";
  echo " 33[1;31m $0 33[m33[1;32m filename33[m.";
  echo "eg. 33[1;31m $033[m33[1;32m  HelloWord.c 33[m";
  exit;
fi

# 2. Does file $1 exist?  s_temp is the evaluation
full_filename="";
full_filename=`find . -name $1`;

#if [[ ! -f $1 ]]; then  # file does not exist
if [[ ${#full_filename} == 0 ]]; then  # file does not exist
    echo "File 33[1;32m$133[m does33[1;31m not exist33[m!";
    s_temp=0;
else
    # in this block, you can call other tools or write scripts for evaluating
        # call foreign tools or scripts             # return a score 
        # ......
        s_temp=`$WorkSpace/tools/sloccount/c_count $full_filename | sed -n '$p'`;   # sed -n  '$p'取最后一行数据

        echo "Lines of $full_filename  is $s_temp";
fi

# 3. export global variable
let score_1=s_temp;
export score_1

  • 还可以有其他审查项目,需要老师(助教)自己编写检查器,比如可以:

      1. 编译待测代码. 可编译,给一个评测分 s2
      1. 运行N 个测试用例,若通过测试数量 n,则 给出评测得分 s3= totalOfTesting * n/N

2.5 系统文件组织结构及输出

  • 文件结构组织
    ..workspace --
                       |-- CheckFramework.sh
                       |-- CheckersCaller.sh
                       |-- Checker_1.sh
                       |-- Checker_2.sh
                       |--  .... ...
                       |-- Checker_n.sh
                       |-- 	oos--
                                       |-- sloccount .....   # 一组代码行计数工具, 这些工具供给 检查器 ``Checker_i.sh``使用
                                       |-- ********* .....   # 其他软件度量工具
                                       |--  .....                    # 
                       |-- RepositoriesName                    # clone 到本地的远程仓库
                       |-- result.csv                          # 检查得分,与分支名对应 (不同的学生对应不同的分支)
                       |-- branches.txt                        # 临时文件,存放学生分支 (不同的学生对应不同的分支,命名规则 SEXXX ,XXX 学号后三位

  • 输出 。 输出文件 result.csv

3. 其他的思考

3.1 本文的方法

  • 本文方法的需要老师自己编写检查器(Checker_i.sh),工作量有些大,本文对检查器的接口做了定义,具体检查内容需要老师自己定义。
  • 另外,对学生提交程序的接口有较严格的要求。建议老师先建立项目,定义好各类接口,学生 fork 该项目。

3.2 其他方案

  • 是不是还可以借助 git push 机制,触发自动检查、评分的。 目前,不清楚
  • 本想利用 Jenkins 持续集成的方案,设计触发器的,感觉有点复杂,设置了几次,跑不起来,若有谁搞通了,最好也写篇博客;
原文地址:https://www.cnblogs.com/juking/p/9733978.html