结对编程实验报告简易在线考试系统
组员学号:2452520、2452512
开发环境:Visual Studio + C# WinForm
完成功能:题目管理、考生答题、自动判分、错题解析报告、限时考试
一、实验概述
本次实验围绕结对编程模式,两人一组合作完成简易在线考试系统的设计与实现。系统包含题目管理、考生答题、自动判分、错题解析、限时考试等核心功能,严格遵循结对编程规范:一人编码、一人审核、定时轮换角色,最终完成可稳定运行的完整考试系统。
二、结对编程模式说明
本次实验全程采用标准结对编程方式开展:
-
角色分工
驾驶员(Driver):负责编写代码、实现功能、调试界面
观察员(Navigator):负责审核代码、检查逻辑、发现BUG、提出优化建议 -
定时轮换
每完成一个模块(题目录入/答题界面/判分逻辑),两人立即交换角色,确保双方都参与设计、编码、测试全过程。 -
全程协作
所有需求讨论、界面布局、逻辑判断、BUG修复均双人共同完成,不单独写代码、不单独提交结果。
三、需求分析(结对共同完成)
通过双人讨论,我们明确系统必须实现以下功能: -
题目管理:支持单选、多选、判断题录入、保存
-
考生答题:限时考试、上下题切换、最后一题自动变为提交
-
自动判分:实时判断对错、生成错题与解析报告
-
流程规范:未开始不能答题、交卷后锁定控件、不重复记录错题
-
界面友好:布局整齐、操作简单、符合真实考试流程
四、系统设计(结对讨论确定) -
三层界面结构
Tab1:题目管理(录入、保存、持久化到文件)
Tab2:在线答题(倒计时、题目、选项、上下题按钮)
Tab3:成绩报告(错题、答案、解析、判分结果) -
题目类设计
基类:Question
子类:单选题、多选题、判断题 -
数据存储
使用 TXT 文件保存题目,实现重启不丢失。
五、结对编程开发过程(重点体现) -
模块1:题目管理界面(A驾驶,B审核)
A 负责搭建输入框、按钮、题型选择器
B 实时检查控件命名、布局合理性、空值判断逻辑
双人共同确定保存格式:编号|题型|题目|选项|答案`
B 发现未判断非空问题,A 立即修正,一次通过 -
模块2:在线答题界面(B驾驶,A审核)
B 编写动态控件生成、TableLayoutPanel 布局
A 提出优化:一行一个选项、按钮放在最下方
双人协作解决:开始考试前禁用所有答题控件
A 发现布局错乱问题,两人共同调整,快速修复 -
模块3:答题与判分逻辑(A驾驶,B审核)
A 实现 RadioButton/CheckBox 原生选择题控件
B 检查判分逻辑,提出必须防止重复记录错题
双人共同设计:用 HashSet 记录已答题,避免重复报告
B 发现“上一题/下一题切换重复生成错题”BUG,两人一起定位并修复 -
模块4:交卷与成绩统计(B驾驶,A审核)
B 实现提交考试、结束考试、时间到三种交卷方式
A 提出:三种交卷效果必须完全统一
双人共同封装FinishExam()统一方法,代码更简洁、健壮
A 检查成绩报告格式,确保题目、答案、解析完整显示 -
整体测试与优化(双人共同测试)
两人轮流进行考试模拟
发现并修复:
按钮未禁用
错题重复记录
最后一题不变提交
结束考试不生成成绩
所有问题均双人快速定位、共同解决,效率远高于单人开发
六、核心功能展示
- 题目录入:支持单选、多选、判断,自动保存到文件
- 限时考试:倒计时显示,时间到自动交卷
- 智能答题:
未开始不能答题
一行一个选项
最后一题自动变为“提交考试” - 自动判分:实时提示正确/错误
七、核心代码展示
点击展开重构后的完整源代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace Pair_Program
{
public partial class Form1 : Form
{
private int allsecond;
private bool isrun = false;
private int index;
private static int id = 0;
private string question;
private string ch1;
private string ch2;
private string ch3;
private string ch4;
private string answer;
private string answeranalyze;
// 已作答题目记录(防止重复记录错题)
private HashSet
List<Question> questionList = new List<Question>();
private int currentQIndex = 0;
private Label lblTitle;
private Label lblOptA, lblOptB, lblOptC, lblOptD;
private TextBox txtUserAns;
private TextBox txtResult;
// 单选题控件(圆形,只能选一个)
private RadioButton radA, radB, radC, radD;
// 多选题控件(方形,可多选)
private CheckBox chkA, chkB, chkC, chkD;
public Form1()
{
InitializeComponent();
// 计时器初始化
allsecond = 60 * 600;
Timelabel.Text = "60:00";
btnPrev.Enabled = false; // 上一题 禁用
btnNext.Enabled = false; // 下一题 禁用
tableLayoutPanel2.Enabled = false; // 题目选项区域 禁用
LoadQuestionsFromTxt();
CreateExamControls();
CreateResultTextBox();
if (questionList.Count > 0)
LoadCurrentQuestion();
}
private void CreateExamControls()
{
// 只用你的 tableLayoutPanel2,绝不乱改布局
TableLayoutPanel panel = tableLayoutPanel2;
// ======================
// 第 0 行:题目 (跨2列)
// ======================
lblTitle = questionlabel;
lblTitle.Font = new Font("微软雅黑", 14, FontStyle.Bold);
lblTitle.Dock = DockStyle.Fill;
lblTitle.TextAlign = ContentAlignment.MiddleLeft;
panel.Controls.Add(lblTitle, 0, 0);
panel.SetColumnSpan(lblTitle, 1);
// ======================
// 第 1 行:选项 A
// ======================
radA = new RadioButton();
radA.Font = new Font("微软雅黑", 12);
radA.Dock = DockStyle.Fill;
panel.Controls.Add(radA, 1, 1);
panel.SetColumnSpan(radA, 1);
// ======================
// 第 2 行:选项 B
// ======================
radB = new RadioButton();
radB.Font = new Font("微软雅黑", 12);
radB.Dock = DockStyle.Fill;
panel.Controls.Add(radB, 1, 2);
panel.SetColumnSpan(radB, 1);
// ======================
// 第 3 行:选项 C
// ======================
radC = new RadioButton();
radC.Font = new Font("微软雅黑", 12);
radC.Dock = DockStyle.Fill;
panel.Controls.Add(radC, 1, 3);
panel.SetColumnSpan(radC, 1);
// ======================
// 第 4 行:选项 D
// ======================
radD = new RadioButton();
radD.Font = new Font("微软雅黑", 12);
radD.Dock = DockStyle.Fill;
panel.Controls.Add(radD, 1, 4);
panel.SetColumnSpan(radD, 1);
}
private void CreateResultTextBox()//生成结果
{
Panel p = tabControl1.TabPages[2];
p.Controls.Clear();
txtResult = new TextBox();
txtResult.Dock = DockStyle.Fill;
txtResult.Multiline = true;
txtResult.ReadOnly = true;
txtResult.ScrollBars = ScrollBars.Vertical;
txtResult.Font = new Font("微软雅黑", 11);
p.Controls.Add(txtResult);
}
private void LoadCurrentQuestion()
{
if (lblTitle == null || radA == null) return;
if (currentQIndex >= questionList.Count) return;
Question q = questionList[currentQIndex];
lblTitle.Text = $"第{currentQIndex + 1}题:{q.QuestionText}";
radA.Checked = false;
radB.Checked = false;
radC.Checked = false;
radD.Checked = false;
radA.Text = "A:" + q.Choices[0];
radB.Text = "B:" + q.Choices[1];
radC.Text = "C:" + q.Choices[2];
radD.Text = "D:" + q.Choices[3];
// ====================== 关键代码 ======================
// 判断是不是最后一题 → 自动切换按钮文字
if (currentQIndex == questionList.Count - 1)
{
btnNext.Text = "提交考试";
}
else
{
btnNext.Text = "下一题";
}
}
// 上一题按钮
private void btnPrev_Click(object sender, EventArgs e)
{
// 加空判断,杜绝崩溃
if (questionList == null || currentQIndex <= 0) return;
currentQIndex--;
LoadCurrentQuestion();
}
// 下一题按钮
private void btnNext_Click(object sender, EventArgs e)
{
if (questionList == null || currentQIndex >= questionList.Count) return;
// 获取答案
string userAns = "";
if (radA.Checked) userAns = "A";
else if (radB.Checked) userAns = "B";
else if (radC.Checked) userAns = "C";
else if (radD.Checked) userAns = "D";
//if (string.IsNullOrWhiteSpace(userAns))
//{
// MessageBox.Show("请选择一个答案!");
// return;
//}
string rightAns = questionList[currentQIndex].AnswerText.Trim();
bool isRight = userAns.Equals(rightAns, StringComparison.OrdinalIgnoreCase);
// ====================== 修复:只记录第一次答案 ======================
if (!answeredQuestions.Contains(currentQIndex))
{
txtResult.AppendText($"【第{currentQIndex + 1}题】\r\n");
txtResult.AppendText($"题目:{questionList[currentQIndex].QuestionText}\r\n");
txtResult.AppendText($"你的答案:{userAns}\r\n");
txtResult.AppendText($"正确答案:{rightAns}\r\n");
txtResult.AppendText($"结果:{(isRight ? "答对" : "答错")}\r\n");
txtResult.AppendText("-----------------------------------------\r\n\r\n");
answeredQuestions.Add(currentQIndex); // 标记已答
}
// 提交考试
if (btnNext.Text == "提交考试")
{
FinishExam();
return;
}
currentQIndex++;
LoadCurrentQuestion();
}
private void FinishExam()//结束考试
{
timer1.Stop();
Timebutton.Text = "开始考试";
isrun = false;
btnPrev.Enabled = false;
btnNext.Enabled = false;
tableLayoutPanel2.Enabled = false;
MessageBox.Show("✅ 考试已完成!成绩已生成。");
tabControl1.SelectedIndex = 2;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex == 0)
{
choice3.Visible = true;
choice4.Visible = true;
index = 0;
}
if (comboBox1.SelectedIndex == 1)
{
choice3.Visible = true;
choice4.Visible = true;
index = 1;
}
if (comboBox1.SelectedIndex == 2)
{
choice3.Visible = false;
choice4.Visible = false;
index = 2;
}
}
// ========== 完整计时器 ==========
private void timer1_Tick(object sender, EventArgs e)
{
allsecond--;
int minute = allsecond / 600;
int second = allsecond % 600 / 10;
Timelabel.Text = $"{minute:D2}:{second:D2}";
if (allsecond <= 0)
{
timer1.Stop();
Timebutton.Text = "开始考试";
isrun = false;
// 时间到 → 禁用
btnPrev.Enabled = false;
btnNext.Enabled = false;
tableLayoutPanel2.Enabled = false;
MessageBox.Show("⏰ 时间到!考试结束");
tabControl1.SelectedIndex = 2;
}
}
private void Timebutton_Click(object sender, EventArgs e)
{
if (!isrun)
{
if (questionList.Count == 0)
{
MessageBox.Show("请先添加题目!");
return;
}
// 开始考试
btnPrev.Enabled = true;
btnNext.Enabled = true;
tableLayoutPanel2.Enabled = true;
txtResult.Clear();
currentQIndex = 0;
answeredQuestions.Clear();
allsecond = 60 * 600;
timer1.Start();
Timebutton.Text = "结束考试";
isrun = true;
tabControl1.SelectedIndex = 1;
LoadCurrentQuestion();
}
else
{
// 结束考试 = 直接交卷(和提交考试一样)
FinishExam();
}
}
private void Submitbutton_Click(object sender, EventArgs e)
{
if (QuestionText.Text == "" || Answer.Text == "" || AnswerAnalyze.Text == "" || choice1.Text == "" || choice2.Text == "")
{
MessageBox.Show("请填写完整信息!");
return;
}
id++;
question = QuestionText.Text;
answer = Answer.Text;
answeranalyze = AnswerAnalyze.Text;
ch1 = choice1.Text;
ch2 = choice2.Text;
Question q = null;
if (index == 0)
{
if (choice3.Text == "" || choice4.Text == "")
{
MessageBox.Show("多选题需填写4个选项!");
return;
}
ch3 = choice3.Text;
ch4 = choice4.Text;
q = new MultipleAnswersQuestion(id, question, answer, ch1, ch2, ch3, ch4);
}
if (index == 1)
{
if (choice3.Text == "" || choice4.Text == "")
{
MessageBox.Show("单选题需填写4个选项!");
return;
}
ch3 = choice3.Text;
ch4 = choice4.Text;
q = new MultipleChoiceQuestion(id, question, answer, ch1, ch2, ch3, ch4);
}
if (index == 2)
{
q = new TrueFalseQuestion(id, question, answer);
}
questionList.Add(q);
SaveQuestionsToTxt();
MessageBox.Show("题目保存成功!");
QuestionText.Clear();
Answer.Clear();
AnswerAnalyze.Clear();
choice1.Clear();
choice2.Clear();
choice3.Clear();
choice4.Clear();
LoadCurrentQuestion();
}
private void SaveQuestionsToTxt()
{
string path = Path.Combine(Application.StartupPath, "questions.txt");
List<string> lines = new List<string>();
foreach (var item in questionList)
{
string type = "";
if (item is MultipleChoiceQuestion) type = "单选";
if (item is MultipleAnswersQuestion) type = "多选";
if (item is TrueFalseQuestion) type = "判断";
string c1 = "", c2 = "", c3 = "", c4 = "";
if (item.Choices != null && item.Choices.Length >= 4)
{
c1 = item.Choices[0];
c2 = item.Choices[1];
c3 = item.Choices[2];
c4 = item.Choices[3];
}
lines.Add($"{item.Id}|{type}|{item.QuestionText}|{c1}|{c2}|{c3}|{c4}|{item.AnswerText}");
}
File.WriteAllLines(path, lines, Encoding.UTF8);
}
private void LoadQuestionsFromTxt()
{
string path = Path.Combine(Application.StartupPath, "questions.txt");
if (!File.Exists(path)) return;
string[] lines = File.ReadAllLines(path, Encoding.UTF8);
questionList.Clear();
id = 0;
foreach (string line in lines)
{
string[] p = line.Split('|');
if (p.Length < 8) continue;
int qid = int.Parse(p[0]);
string type = p[1];
string title = p[2];
string c1 = p[3];
string c2 = p[4];
string c3 = p[5];
string c4 = p[6];
string ans = p[7];
id = qid;
Question q = null;
if (type == "单选")
q = new MultipleChoiceQuestion(qid, title, ans, c1, c2, c3, c4);
else if (type == "多选")
q = new MultipleAnswersQuestion(qid, title, ans, c1, c2, c3, c4);
else if (type == "判断")
q = new TrueFalseQuestion(qid, title, ans);
if (q != null) questionList.Add(q);
}
}
}
}
点击展开重构后的完整源代码
using System;namespace Pair_Program
{
public class Question
{
protected int id;
protected string question;
protected int type;
protected string answer;
protected string[] choice;
public int Id => id;
public string QuestionText => question;
public string AnswerText => answer;
public string[] Choices => choice;
public Question() { }
public Question(int id, string question, int type, string answer, string[] choice)
{
this.id = id;
this.question = question;
this.type = type;
this.answer = answer;
this.choice = choice;
}
}
}
点击展开重构后的完整源代码
八、核心效果展示





九、实验总结
通过本次结对编程实验,我们不仅顺利完成了简易在线考试系统的全部功能,更深刻理解了结对编程的价值:
提高代码质量、减少BUG、提升效率、促进学习、强化协作。
整个开发过程中,我们从需求分析到界面设计,从代码编写到系统测试,全程保持双人协作、角色轮换、共同决策,最终完成了稳定、完整、符合实验要求的在线考试系统。
这次实践让我们真正体会到:
好的软件不是写出来的,是协作出来的。

浙公网安备 33010602011771号