软件工程——结对项目——四则运算题目生成
队友博客地址:https://blog.csdn.net/qq_37248820/article/details/86301669
-------------------------------------------------------------------------------------------------------------------
实现一个能够自动生成小学四则运算题目的Windows控制台程序,最后扩展为一个Windows图形界面程序。
第一阶段:
a. 能够生成一千道小学四则运算题目到一个文件里,保证合法、不重复。
b. 可以设置乘方的符号选择( ** 或 ^ )。
c. 能解最多包含10个运算符的表达式(其中括号数不受限制,运算符有+,-,*,/,^ (或 **) 五种)。
d. 除了整数之外,还支持真分数的四则运算。
e. 能接受用户输入的答案,并判定对错,统计总共的对/错数量。
第二阶段:
和队友商量后,决定选择第一个扩展方向进行扩展。
a. 把程序变成一个Windows电脑图形界面的程序。
b. 增加一个日历显示器,显示当前时间。
c. 增加倒计时功能,每个题目必须在20秒内完成。
d. 增加历史记录功能,可以保存并展示用户的做题成绩记录。
-------------------------------------------------------------------------------------------------------------------
文章目录
一、GitHub项目地址
项目的GitHub地址:https://github.com/BeckyYang/Arithmetic_SoftwareEngineeringTeamProject
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(min) | 实际耗时(min) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
Estimate | 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 2880 | 2160 |
Analysis | 需求分析(包括学习新技术) | 1440 | 1460 |
Design Spec | 生成设计文档 | 0 | 0 |
Design Review | 设计复审(和同事审核设计文档) | 0 | 0 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 0 | 0 |
Design | 具体设计 | 60 | 60 |
Coding | 具体编码 | 5760 | 5760 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 1000 | 2000 |
Reporting | 报告 | 0 | 0 |
Test Report | 测试报告 | 60 | 120 |
Size Measurement | 计算工作量 | 60 | 0 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 60 | 180 |
合计 | 11360 | 11900 |
三、实现思路
拿到题目之后,和队友讨论了一下分工合作,决定我负责题目生成部分,她负责题目求解部分,最后的扩展部分由我们两人共同完成。
题目分为两种类型:整数四则运算和分数四则运算,不包括整数和分数混合运算。
3.1 控制台程序的题目生成
初步思路:
- 将分数用一个类来表示,包括分子和分母属性。分数的随机生成可通过随机生成分子和分母来实现。
- 通过rand()函数来随机生成数和运算符(控制在1到10个之间,不包括括号),将二者组合可得到不同的表达式。
- 再通过rand()随机生成括号。
后来在初步实现时,发现出现了随机生成的数太大,乘方的幂不是整数,表达式包含的运算符普遍偏多,随机数每次重复出现,没有做到真正随机化的情况,这样的难度并不适合小学生。于是做了如下的改进。
改进思路:
- 将随机生成的数%10,这样可控制数字在0到9范围内。
- 乘方后面的数采用整数形式,将随机生成的乘方的幂%3,这样可以控制幂在0到2范围内。
- 采用正态分布来控制运算符个数和括号个数,使得大部分表达式的运算符个数为3,括号个数为3。
- 使用 time函数来获得系统时间,然后将time_t型数据转化为(unsigned)型再传给随机数引擎。说明:一个给定的随机数发生器一直会生成相同的随机数序列,所以随机数引擎的参数一般用time(NULL),因为系统的时间一直在变,所以rand()获得的数,也就一直在变,相当于是随机数了。
- 判重部分:主要通过运算过程来判断题目是否重复。通过对操作数栈和运算符栈进行操作,将目前已生成的所有不重复题目的运算过程保存在运算过程记录区stack_order[ ][ ]中。之后,每生成一个题目,将其运算过程与stack_order中的每一行进行比较,若相同,则重复,重新生成题目;否则,将其运算过程存入stack_order[ ][ ]中。
3.2 Windows图形界面程序的题目生成
主要设计4个窗体界面:
- 欢迎主界面:含有一个“开始”按键,用户点击即可进入到操作请求界面。
- 操作请求界面:含有一个日历显示器,显示当前日期。用户输入用户名、题目数量和题目类型(默认为A(没有乘方的普通运算),也可选择B(乘方用^表示)或C(乘方用**表示))并点击Begin按键,即可开始答题。点击Record按键可查询历史答题记录(每一条记录由题目,True or False , 用户答案, 正确答案组成,因超时而未做答的题不予以记录);点击Quit按键,可推出整个程序。
- 答题界面:输入答案,点击OK键可提交答案(答案必须为最简形式,假分数需表示为整数+(或-)真分数),提交后显示答题结果和正确答案;另外在答题界面设置一个20秒的倒计时器,时间一到,提醒用户并切换到下一题。点击Cancle键可提出答题界面,回到操作请求界面,继续操作。
- 记录查询界面:点击Query即可查询历史记录;点击Quit即可回到操作请求界面,执行其他操作。
基于C++版本的实现改进:
- 由于没有C#的基础,所以只能边学边做。为了控制题目的长度,在用C#实现满足正态分布的随机数时,因为没有现成的函数可以调用,所以参照了网上的部分代码。在未采用之前,题目长度普遍偏长,难度偏大,不适合小学生做。(参照的正态分布随机函数代码地址)
- 另外,C#版本相对于C++版本的控制台程序作了一定改进:在判重时,将整数也用分数表示,统一采用分数类fraction的重载运算符来计算,这样可以很好地解决“/”对于整数而言为整除的问题,使得结果更加准确。
四、生成题目代码设计实现
由于代码太长,仅说明部分函数。完整代码链接
生成题目的主要函数包括:
int GetGCD(int a, int b); //求分子a好分母b的最大公约数
int RandomSymbolGenerate(char type); //随机生成运算符
int RandLeftBracketGenerate(); //随机生成左括号
int RandRightBracketGenerate(); //随机生成右括号
int RandIntegerGeneate(); //随机生成0到9的整数
fraction& RandFractionGeneate(); //随机生成真分数(不包括0和1),分子和分母均在0到9之间
int IntPartialResult(int A, int B, int sym); //两个整数的四则运算
fraction& FractionPartialResult(fraction &A, fraction& B, int sym); // 两个分数的四则运算
int Check(int puzzle[], int puzzle_len, int puzzle_num, int num_type); //判断题目是否重复
void PuzzleGenerate(char* argv, int N, char type); //生成N个四则表达式
生成表达式部分:通过借助随机函数rand(), 来随机生成操作数和运算符以及括号。将三者组合形成一个表达式。
- 随机生成0到9的整数
int RandIntegerGeneate() //随机生成0到9的整数
{
int number = rand() % 10;
if (number < 0) number = 0;
return number;
}
- //随机生成真分数(不包括0和1),分子和分母均在0到9之间
fraction& RandFractionGeneate() //随机生成真分数(不包括0和1),分子和分母均在0到9之间
{
int t;
int rand_numerator = rand() % 10;
if (rand_numerator <= 0) rand_numerator = 1;
int rand_denominator = rand() % 10;
if (rand_denominator <= 0) rand_denominator = 1;
if (rand_numerator > rand_denominator)
{
t = rand_numerator;
rand_numerator = rand_denominator;
rand_denominator = t;
}
else if (rand_numerator == rand_denominator)
{
rand_denominator += 1;
}
int GCD = GetGCD(rand_numerator, rand_denominator);
fraction number(rand_numerator/GCD, rand_denominator/GCD);
return number;
}
- int RandLeftBracketGenerate(); //随机生成左括号
int RandLeftBracketGenerate() //随机生成左括号
{
int left_bracket = rand() % 2;
return left_bracket;//1表示当前生成左括号,0表示当前不生成左括号
}
- int RandRightBracketGenerate(); //随机生成右括号
int RandRightBracketGenerate() //随机生成右括号
{
int right_bracket = rand() % 2;
return right_bracket; //1表示当前生成右括号,0表示当前不生成右括号
}
判重部分:主要借助栈来使实现题目的判重。
- 运算符优先级:
运算符 | ( 、) | + 、- | * 、/ | ^ 、** |
---|---|---|---|---|
优先级 | 0 | 1 | 2 | 3 |
- 记录题目运算过程流程图:
- 将当前题目的解题过程order[]与stack_order[][]中的所有题目的解题过程比较,若重复,则重新生成;否则将order[]记录到stack_order[][]中。
- 判重部分实现代码
int Check(int puzzle[], int puzzle_len, int puzzle_num, int num_type) //判断题目是否重复
{
int i, j, L = 1, s, A, B, t, first = 0;
fraction FA, FB, Ft;
stack<int> stack_integer;
stack<fraction> stack_fraction;
stack<int> stack_operator;
int order[600];
memset(order, 0, sizeof(order));
/********整数表达式*******/
if (num_type == 0)
{
for (i = 0; i < puzzle_len; i++)
{
if (puzzle[i] < 0) continue;
if (puzzle[i] < 100 && puzzle[i] >=0) //遇到数字,数字直接进入stack_integer栈
{
stack_integer.push(puzzle[i]);
}
else
{
if (puzzle[i] == 106) //遇到左括号,左括号直接入stack_operator栈
{
stack_operator.push(106);
}
else if (puzzle[i] == 107) //遇到右括号,弹出所有运算符,直至遇到左括号
{
if (!stack_operator.empty())
{
do
{
s = stack_operator.top(); //运算符出栈
stack_operator.pop();
B = stack_integer.top(); //操作数B出栈
stack_integer.pop();
A = stack_integer.top(); //操作数A出栈
stack_integer.pop();
if (s == 100 || s == 102) //+,*法,统一按照大数在前,小数在后方式运算
{
if (A < B)
{
t = A;
A = B;
B = t;
}
}
order[L++] = A;
order[L++] = s;
order[L++] = B;
if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
{
return -1;
}
stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
} while (stack_operator.empty() == false && stack_operator.top() != 106);
if (!stack_operator.empty()) stack_operator.pop(); //弹出左括号
}
}
else
{
if (puzzle[i] == 104 || puzzle[i] == 105) //遇到乘方,乘方优先级最高,乘方直接进运算符栈
{
stack_operator.push(puzzle[i]);
}
else
{
//遇到运算符,且运算符栈为空或者栈顶运算符优先级小于当前运算符优先级,运算符直接进运算符栈
if (stack_operator.empty() == true || (stack_operator.empty() == false && priority[stack_operator.top()-100] < priority[puzzle[i]-100])) stack_operator.push(puzzle[i]);
else
{
do
{
s = stack_operator.top(); //运算符出栈
stack_operator.pop();
B = stack_integer.top(); //操作数B出栈
stack_integer.pop();
A = stack_integer.top(); //操作数A出栈
stack_integer.pop();
if ((s == 100 || s == 102) && A < B) //+,*法,统一按照大数在前,小数在后方式运算
{
t = A;
A = B;
B = t;
}
order[L++] = A;
order[L++] = s;
order[L++] = B;
if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
{
return -1;
}
stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
} while (stack_operator.empty() == false && priority[stack_operator.top() - 100] >= priority[puzzle[i]-100]);
stack_operator.push(puzzle[i]); //运算符进运算符栈
}
}
}
}
}
if (stack_operator.empty() == false)
{
do
{
s = stack_operator.top(); //运算符出栈
stack_operator.pop();
B = stack_integer.top(); //操作数B出栈
stack_integer.pop();
A = stack_integer.top(); //操作数A出栈
stack_integer.pop();
if ((s == 100 || s == 102) && A < B) //+,*法,统一按照大数在前,小数在后方式运算
{
t = A;
A = B;
B = t;
}
order[L++] = A;
order[L++] = s;
order[L++] = B;
if ((s == 103 && B == 0) || ((s == 104 || s == 105) && A == 0 && B == 0))
{
return -1;
}
stack_integer.push(IntPartialResult(A, B, s)); //运算结果入栈
}while (stack_operator.empty() == false);
}
order[0] = L;
}
/****分数表达式****/
else
{
for (i = 0; i < puzzle_len; i++)
{
if (puzzle[i] < 0) continue;
if (puzzle[i] < 100 && puzzle[i] >= 0) //遇到分数,分数直接进入stack_fraction栈
{
fraction f(puzzle[i], puzzle[i + 2]);
stack_fraction.push(f);
i += 2;
}
else
{
if (puzzle[i] == 106) //遇到左括号,左括号直接入stack_operator栈
{
stack_operator.push(106);
}
else if (puzzle[i] == 107) //遇到右括号,弹出所有运算符,直至遇到左括号
{
s = stack_operator.top(); //运算符出栈
do
{
stack_operator.pop();
FB = stack_fraction.top(); //操作数FB出栈
stack_fraction.pop();
FA = stack_fraction.top(); //操作数FA出栈
stack_fraction.pop();
if (s == 100 || s == 102) //+,*法,统一按照大数在前,小数在后方式运算
{
if (FA < FB)
{
Ft = FA;
FA = FB;
FB = Ft;
}
}
order[L++] = FA.GetNumerator();
order[L++] = 1030;
order[L++] = FA.GetDenominator();
order[L++] = s;
order[L++] = FB.GetNumerator();
order[L++] = 1030;
order[L++] = FB.GetDenominator();
if (FB.GetNumerator() == 0 && s == 103) return -1;
fraction ff = FractionPartialResult(FA, FB, s);
stack_fraction.push(ff); //运算结果入栈
} while (stack_operator.empty() == false && (s = stack_operator.top()) != 106);
stack_operator.pop(); //弹出左括号
}
else
{
//遇到运算符,且运算符栈为空或者栈顶运算符优先级小于当前运算符优先级,运算符直接进运算符栈
if (stack_operator.empty() == true || (stack_operator.empty() == false && priority[stack_operator.top() - 100] < priority[puzzle[i] - 100])) stack_operator.push(puzzle[i]);
else
{
do
{
s = stack_operator.top(); //运算符出栈
stack_operator.pop();
FB = stack_fraction.top(); //操作数B出栈
stack_fraction.pop();
FA = stack_fraction.top(); //操作数A出栈
stack_fraction.pop();
if ((s == 100 || s == 102) && FA < FB) //+,*法,统一按照大数在前,小数在后方式运算
{
Ft = FA;
FA = FB;
FB = Ft;
}
order[L++] = FA.GetNumerator();
order[L++] = 1030;
order[L++] = FA.GetDenominator();
order[L++] = s;
order[L++] = FB.GetNumerator();
order[L++] = 1030;
order[L++] = FB.GetDenominator();
if (FB.GetNumerator() == 0 && s == 103) return -1;
fraction ff = FractionPartialResult(FA, FB, s);
stack_fraction.push(ff); //运算结果入栈
} while (stack_operator.empty() == false && priority[stack_operator.top() - 100] >= priority[puzzle[i] - 100]);
stack_operator.push(puzzle[i]); //运算符进运算符栈
}
}
}
}
if (stack_operator.empty() == false)
{
do
{
s = stack_operator.top(); //运算符出栈
stack_operator.pop();
FB = stack_fraction.top(); //操作数B出栈
stack_fraction.pop();
FA = stack_fraction.top(); //操作数A出栈
stack_fraction.pop();
if ((s == 100 || s == 102) && FA < FB) //+,*法,统一按照大数在前,小数在后方式运算
{
Ft = FA;
FA = FB;
FB = Ft;
}
order[L++] = FA.GetNumerator();
order[L++] = 1030;
order[L++] = FA.GetDenominator();
order[L++] = s;
order[L++] = FB.GetNumerator();
order[L++] = 1030;
order[L++] = FB.GetDenominator();
if (FB.GetNumerator() == 0 && s == 103) return -1;
fraction ff = FractionPartialResult(FA, FB, s);
stack_fraction.push(ff); //运算结果入栈
} while (stack_operator.empty() == false);
}
order[0] = L;
}
/**********判断与之前的题目是否重复************/
int flag = 0, label = 0;
for (i = 0; i < puzzle_num; i++)
{
if (labels[i] != num_type || order[0] != stack_order[i][0]) continue;
else
{
flag = 0;
for (j = 1; j < stack_order[i][0]; j++)
{
if (stack_order[i][j] != order[j])
{
flag = 1;
break;
}
}
if (flag == 0)
{
label = 1;
break;
}
}
}
if (label == 1) return -1; //有重复
else
{
for (j = 0; j < order[0]; j++) //登记到题目记录里
{
stack_order[puzzle_num][j] = order[j];
}
return 1; //无重复
}
}
五、程序性能分析
由性能分析报告可知,题目生成和查重函数比较耗时,而题目生成的主要调用函数也是Check()函数。因此仅需要对查重函数Check()进行改进即可。对其的改进为:设置一个题型标记数组Labels[],在判重时,优先判断题型是否一样,再判断解题过程的长度是否一样,最后再判断解题过程内容是否完全一样,这样可以减少判重时间。
六、测试数据
主要测试了题目查重和分数类运算符重载部分。
1、分数类运算符重载测试:
因为分数类运算符重载是最底层的重要部分,会受到上层函数的多次调用, 一旦这一部分出现错误,将直接导致上层函数的错误。所以对于分数类运算符重载部分的测试是有必要的。
测试用例如下:
数A | 运算符 | 数B | 预期结果 |
---|---|---|---|
2/3 | ^ | 0 | 1 |
2/3 | * | 2/3 | 4/9 |
2/3 | + | 1 | 5/3 |
2/3 | - | 1/3 | 1/3 |
6 | 求最大公约数 | 10 | 2 |
2 | 求最大公约数 | 3 | 1 |
5 | 求最大公约数 | 10 | 5 |
2、题目查重测试:
因为题目重复主要是由加法和乘法的交换律,以及括号的左结合性导致的,所以题目查重部分的测试主要针对 + 、* 、和()以及三者的结合进行测试。
测试用例如下:
表达式1 | 表达式2 | 预期结果 |
---|---|---|
1+2+3 | 3+(2+1) | 重复 |
1+2+3 | 3+2+1 | 不重复 |
1 * 2 * 3 | 3 * 2 * 1 | 不重复 |
(1+2) * 3 | 3 * (2+1) | 重复 |
1+2 ** 3 ** 1 | 2 ** 3 ** 1 + 1 | 重复 |
(1+(1+2)*(3-2))+1 | 1+((3-2)*(1+2))+1) | 不重复 |
(1+(1+2)*(3-2))+1 | 1+((1+2)*(3-2))+1) | 重复 |
1+2 ** 3 ** 1 | 2 ** 3 ** 1 + 1 | 重复 |
1/2 + (1/3 + 1/4) | 1/4 + 1/3 + 1/2 | 重复 |
1/2 * (1/3 + 1/4) | (1/4 + 1/3) * 1/2 | 重复 |
3、测试代码
namespace ArithmeticGUI.Tests
{
[TestClass()]
public class ClassTests
{
//----------------fraction.cs的单元测试------------------
[TestMethod()]//正常情况
public void FracionGetGCDTest1()
{
int x = 6;
int y = 10;
Assert.AreEqual(fraction.FracionGetGCD(x, y), 2);
}
[TestMethod()]//最大公因数为1
public void FracionGetGCDTest2()
{
int x = 2;
int y = 3;
Assert.AreEqual(fraction.FracionGetGCD(x, y), 1);
}
[TestMethod()]//最大公因数为其中较小的数
public void FracionGetGCDTest3()
{
int x = 5;
int y = 10;
Assert.AreEqual(fraction.FracionGetGCD(x, y), 5);
}
//---------------N_Puzzle.cs的单元测试-----------------
//--------------------题目查重测试------------------
//加法交换+括号测试
[TestMethod()]//测试1+2+3与3+(2+1)是否重复,预期结果为重复 -1
public void CheckTest1()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 100, 2, 100, 3 };
int[] puzzle2 = new int[] { 3, 100, 106, 2, 100, 1, 107 };
int puzzle_len1 = 5;
int puzzle_len2 = 7;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//加法顺序交换测试
[TestMethod()]//测试1+2+3与3+2+1是否重复,预期结果为不重复 1
public void CheckTest2()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 100, 2, 100, 3 };
int[] puzzle2 = new int[] { 3, 100, 2, 100, 1 };
int puzzle_len1 = 5;
int puzzle_len2 = 5;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
}
//乘法顺序交换测试
[TestMethod()]//测试1*2*3与3*2*1是否重复,预期结果为不重复 1
public void CheckTest3()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 102, 2, 102, 3 };
int[] puzzle2 = new int[] { 3, 102, 2, 102, 1 };
int puzzle_len1 = 5;
int puzzle_len2 = 5;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
}
//除法顺序交换测试
[TestMethod()]//测试1/2/3与3/2/1是否重复,预期结果为不重复 1
public void CheckTest4()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 103, 2, 103, 3 };
int[] puzzle2 = new int[] { 3, 103, 2, 103, 1 };
int puzzle_len1 = 5;
int puzzle_len2 = 5;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
}
//乘法交换+括号测试
[TestMethod()]//测试(1+2)*3与3*(2+1)是否重复,预期结果为重复 -1
public void CheckTest5()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 106, 1, 100, 2, 107, 102, 3 };
int[] puzzle2 = new int[] { 3, 102, 106, 2, 100, 1 ,107 };
int puzzle_len1 = 7;
int puzzle_len2 = 7;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//^乘方测试
[TestMethod()]//测试1+2^3^1与2^3^1 + 1是否重复,预期结果为重复 -1
public void CheckTest6()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 100, 2, 104, 3, 104, 1 };
int[] puzzle2 = new int[] { 2, 104, 3, 104, 1, 100, 1 };
int puzzle_len1 = 7;
int puzzle_len2 = 7;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//括号嵌套测试
[TestMethod()]//测试 (1+(1+2)*(3-2))+1 与 1+((3-2)*(1+2))+1) 是否重复,预期结果为不重复 1
public void CheckTest7()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 106, 1, 100, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
int[] puzzle2 = new int[] { 1, 100, 106, 106, 3, 101, 2, 107, 102, 106, 1, 100, 2, 107, 107, 100, 1 };
int puzzle_len1 = 17;
int puzzle_len2 = 17;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), 1);
}
//括号嵌套测试+乘法顺序变换
[TestMethod()]//测试 (1+(1+2)*(3-2))+1 与 1+((1+2)*(3-2))+1) 是否重复,预期结果为重复 -1
public void CheckTest8()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 106, 1, 100, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
int[] puzzle2 = new int[] { 1, 100, 106, 106, 1, 100, 2, 107, 102, 106, 3, 101, 2, 107, 107, 100, 1 };
int puzzle_len1 = 17;
int puzzle_len2 = 17;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//**乘方测试
[TestMethod()]//测试1+2**3**1与2**3**1 + 1是否重复,预期结果为重复 -1
public void CheckTest9()
{
N_Puzzle.labels[0] = 0;
N_Puzzle.labels[1] = 0;
int[] puzzle1 = new int[] { 1, 100, 2, 105, 3, 105, 1 };
int[] puzzle2 = new int[] { 2, 105, 3, 105, 1, 100, 1 };
int puzzle_len1 = 7;
int puzzle_len2 = 7;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 0;
int num_type2 = 0;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//分数的加法+括号测试
[TestMethod()]//测试 1/2 + (1/3 + 1/4) 与 1/4 + 1/3 + 1/2 是否重复,预期结果为重复 -1
public void CheckTest10()
{
N_Puzzle.labels[0] = 1;
N_Puzzle.labels[1] = 1;
int[] puzzle1 = new int[] { 1, 1030, 2, 100, 106, 1, 1030, 3, 100, 1, 1030, 4, 107 };
int[] puzzle2 = new int[] { 1, 1030, 4, 100, 1, 1030, 3, 100, 1, 1030, 2 };
int puzzle_len1 = 13;
int puzzle_len2 = 11;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 1;
int num_type2 = 1;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//分数的加法+乘法测试
[TestMethod()]//测试 1/2 * (1/3 + 1/4) 与 (1/4 + 1/3) * 1/2 是否重复,预期结果为重复 -1
public void CheckTest11()
{
N_Puzzle.labels[0] = 1;
N_Puzzle.labels[1] = 1;
int[] puzzle1 = new int[] { 1, 1030, 2, 102, 106, 1, 1030, 3, 100, 1, 1030, 4, 107 };
int[] puzzle2 = new int[] { 106, 1, 1030, 4, 100, 1, 1030, 3, 107, 102, 1, 1030, 2 };
int puzzle_len1 = 13;
int puzzle_len2 = 13;
int puzzle_num1 = 0;
int puzzle_num2 = 1;
int num_type1 = 1;
int num_type2 = 1;
N_Puzzle.Check(puzzle1, puzzle_len1, puzzle_num1, num_type1);
Assert.AreEqual(N_Puzzle.Check(puzzle2, puzzle_len2, puzzle_num2, num_type2), -1);
}
//--------------运算符重载测试-------------------------
[TestMethod()] //分数类 ^ 运算符重载
public void CheckTest12()
{
bool z = false;
fraction A = new fraction (2,3);
fraction B = new fraction (0, 1);
fraction C = A ^ B;
fraction E = new fraction(1, 1);
if (C == E) z = true;
Assert.IsTrue(z);
}
[TestMethod()] //分数类 * 运算符重载
public void CheckTest13()
{
bool z = false;
fraction A = new fraction(2, 3);
fraction B = new fraction(2, 3);
fraction C = A * B;
fraction E = new fraction(4, 9);
if (C == E) z = true;
Assert.IsTrue(z);
}
[TestMethod()] //分数类 / 运算符重载
public void CheckTest14()
{
bool z = false;
fraction A = new fraction(2, 3);
fraction B = new fraction(1, 3);
fraction C = A / B;
fraction E = new fraction(2, 1);
if (C == E) z = true;
Assert.IsTrue(z);
}
[TestMethod()] //分数类 + 运算符重载
public void CheckTest15()
{
bool z = false;
fraction A = new fraction(2, 3);
fraction B = new fraction(1, 1);
fraction C = A + B;
fraction E = new fraction(5, 3);
if (C == E) z = true;
Assert.IsTrue(z);
}
[TestMethod()] //分数类 - 运算符重载
public void CheckTest16()
{
bool z = false;
fraction A = new fraction(2, 3);
fraction B = new fraction(1, 3);
fraction C = A - B;
fraction E = new fraction(1, 3);
if (C == E) z = true;
Assert.IsTrue(z);
}
}
}
4、测试结果
七、C#版实现效果
欢迎主界面
操作请求界面
答题界面
记录查询界面
八、结队项目开发总结
这次结队项目的开发让我认识到了队友的重要性。还记得以前也有一次结队项目,7个人一起完成,但是由于彼此不熟悉,交流不到位,导致任务工作分配不合理,最后对接时,漏洞百出。这一点正好映证了“增加开发人员,不一定能加快项目开发速度。”这一句话。而这一次因为只有两个人,且彼此熟悉,交流起来也就很容易了,所以整体的项目开发还算比较顺利。但是由于时间紧张,程序实现的功能比较简单,如果时间充裕的话,相信我们还能将程序优化得更好。另外,吸取上一次个人项目的开发经验和教训,这一次采用了类的思想,代码组织也更加有序。在实现过程中,我们也遇到了很多问题,主要是初始值被覆盖,访问地址越界等,好在经过人工检测和代码调试最后改正了过来。对于扩展功能的实现,由于没有什么基础,所以在开发过程中学习到了很多新的知识。此外,程序的一个不足之处是检查用户输入答案时,我们限制了结果的表示形式,也许还可以改进一下,允许用户灵活输入。总的来说,有了上一次个人项目的经验,这一次要顺手多了,毕竟熟能生巧。