1.项目成员
董美凤:201521123003
杨雪莹:201521123005
结对编程码云地址:https://gitee.com/yangxueying/pair_programming
原源代码地址:https://coding.net/u/weh/p/software-testing/git
2.需求分析
现有代码主要实现了高年级和低年级加减乘除四则运算,语言选择,选择题数,计算答题对错的数量。
分析原有代码,发现以下问题:
- 所出题目的类型单一,只有三个数字和两个操作符的形式,如a+b-c
- 计算答案的部分也只有按照这种题目类型作出相应的计算,如果所出的题目变成其他形式,就计算不出结果,局限性较大
- 没有对重复题目作出判断
- 高低年级的题目难度差不多,没有作出明显区分
- 源代码的计时模块存在一定问题,有时候计时时间到了会停止做题,有时候又无限计时
- 代码重复量高,同样的代码出现了好几次,出现逻辑泥球,如下图低年级的三种语言的代码是重复的,高年级的三种语言也代码是重复的,界面类也是
作出的修改:
- 重构了随机出题的模块,使得出题类型增加,有更多变换形式
- 当输入答案是分数时,无法判断答案正确与否
- 将计算答案的部分做了重构,采用后缀表达式来计算最终答案,这样写一个方法,同样适用于高年级和低年级的答案计算
- 优化了计时模块,使每道题的做题时间超时会提示退出
新加入的功能:
- 增加括号操作符
- 出题过程中减少重复题目
- 高年级的出题类型增加了乘方的形式
- 当输入答案是分数或者小数形式时,都可判断答案是否正确
3.程序设计
思维导图
类图:
4.代码展示
括号优先级运算以及增加乘方运算,运用后缀表达式,具体代码如下:
package Function;
import java.util.Stack;
public class Result {
private static Stack<String> num = new Stack<String>(); //存后缀表达式
private static Stack<String> sign = new Stack<String>(); //存入符号
private static Stack<Double> result = new Stack<Double>(); //放结果
public static void getGroup(String line){ //将字符串转换为后缀表达式
for(int i=0; i<line.length(); i++){
char c = line.charAt(i);
if((int)c>=48 && (int)c<=57){
//当遇到数字的时候,判断是不是多位数,然后在push进num
int j = i+1;
while(j<line.length() && (line.charAt(j)>=48 && line.charAt(j)<=57)){
j++;
}
num.push(line.substring(i, j));
i = j-1;
}else if(c == '('){
sign.push(String.valueOf(c));
}else if(c == ')'){
while(!sign.peek().equals("(")){
num.push(sign.pop());
}
sign.pop();
}else{
int n = 0;
if(!sign.empty()) { //如果sign中没有元素,直接令n = 0
n = getNum(sign.peek().charAt(0));
}
int m = getNum(c);
if(m >= n){ //如果当前元素的运算级别比栈顶元素运算级别要高,就直接push进sign
sign.push(String.valueOf(c));
}else{
while(m < n){
num.push(sign.pop());
if(!sign.empty()){
n = getNum(sign.peek().charAt(0));
}else{
n = 0;
}
}
sign.push(String.valueOf(c));
}
}
}
while(!sign.empty()){
num.push(sign.pop());
}
}
private static int getNum(char c){
int n = 0;
switch(c){
case '+':
case '-':
n = 1;
break;
case '*':
case '^':
case '/':
n = 2;
break;
}
return n;
}
private static void getResult(){ //得到的后缀表达式反向遍历,遇到数字就加入result,遇到符号就从result中取出两个数进行运算然后将结果加入result
Stack<String> t = new Stack<String>();
while(!num.empty()){
t.push(num.pop());
}
String str = t.pop();
while(str != null){
if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")||str.equals("^")){
double n = result.pop();
double m = result.pop();
double num = 0;
if(str.equals("+")) num = m+n;
if(str.equals("-")) num = m-n;
if(str.equals("*")) num = m*n;
if(str.equals("/")) num = m/n;
if(str.equals("^")) num = Math.pow(m, n);
result.push(num);
}else{
result.push(Double.parseDouble(str));
}
if(!t.empty()){
str = t.pop();
}else{
str = null;
}
}
}
public Double getAnswer(String timu) {
getGroup(timu);
getResult();
return result.peek();
}
}
随机生成题目代码:
package Function;
import java.util.Random;
public class RandomTimu {
public static String DnjcalStringCreate(int r){
char []c={'+','-','*','/'};//操作符数组
Random random=new Random();
StringBuffer str=new StringBuffer();
int n= random.nextInt(3)+1;
int num=random.nextInt(r-1)+1;
str.append(num);
for (int i = 0; i <n ; i++) {//在1到3范围内随机个数的运算符
char c2=c[(int)(c.length* java.lang.Math.random())];//生成随机操作符
int num2=random.nextInt(r-1)+1;//生成大于0小于r的自然数
str.append(c2);
str.append(num2);
}
return str.toString();
}
public static String GnjcalStringCreate(int r){
char []c={'+','-','*','/','^'};//操作符数组
Random random=new Random();
String timustr="";
int n= random.nextInt(3)+1;
int num=random.nextInt(r-1)+1;
timustr=timustr+num;
for (int i = 0; i <n ; i++) {//在1到3范围内随机个数的运算符
char c2=c[(int)(c.length* java.lang.Math.random())];//生成随机操作符
int num2=random.nextInt(r-1)+1;//生成大于0小于r的自然数
if(c2=='^')
{
num2=random.nextInt(3);
}
timustr=timustr+c2;
timustr=timustr+num2;
if(random.nextInt(10)==0&&i<n-1)
{
timustr="("+timustr+")";
}
}
return timustr;
}
}
减少题目重复关键代码:
package Function;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;
public class GetChongFu {
public static List<String> getChoufu(String timu)
{
List<String> chongti = new ArrayList<>();
if(timu.contains("("))
{
List<String> num = new ArrayList<>();
List<String> fuhao = new ArrayList<>();
for(int i=0; i<timu.length(); i++)
{
char c = timu.charAt(i);
if((int)c>=48 && (int)c<=57){//当遇到数字的时候,判断是不是多位数,然后在push进num
int j = i+1;
while(j<timu.length() && (timu.charAt(j)>=48 && timu.charAt(j)<=57)){
j++;
}
num.add(timu.substring(i, j));
}
else
{
fuhao.add(String.valueOf(c));
fuhao.add(String.valueOf(i));
}
}
int cishu=0;
int[] n;
n=new int[3];
for(int i=0;i<fuhao.size();i++)
{
if(fuhao.get(i).equals("+"))
{
cishu++;
if(i==0)
{
String str=num.get(1)+"+"+num.get(0);
for(int j=2;j<fuhao.size();j++)
{
str=fuhao.get(j)+num.get(j+1);
if(fuhao.get(i).equals("+"))
j++;
}
chongti.add(str);
}
if(cishu==2)
{
n[1]=Integer.valueOf(num.get(i+1));
chongti.add(timu.substring(0,n[0]-1)+"+"+timu.substring(n[1]+1,timu.length())+"+"+timu.substring(n[0]+1, n[1]-1));
}
if(cishu==3)
{
n[2]=Integer.valueOf(num.get(i+1));
chongti.add(timu.substring(0,n[1]-1)+"+"+timu.substring(n[2]+1,timu.length())+"+"+timu.substring(n[1]+1, n[2]-1));
chongti.add(timu.substring(0,n[0]-1)+"+"+timu.substring(n[2]+1,timu.length())+"+"+timu.substring(n[0]+1, n[2]-1));
}
n[0]=Integer.valueOf(num.get(i+1));
chongti.add(timu.substring(n[0]+1,timu.length())+"+"+timu.substring(0, n[0]-1));
}
else
{
cishu=0;
if(fuhao.get(i).equals("*"))
{
if(i==0)
{
int d1=Integer.valueOf(num.get(i));
int d2=Integer.valueOf(num.get(i+1));
if(i+1<fuhao.size())
{
chongti.add(timu.substring(d1+1,d2-1)+"*"+timu.substring(0,d1-1)+timu.substring(d2, timu.length()));
}
else
chongti.add(timu.substring(d1+1,d2-1)+"*"+timu.substring(0,d1-1));
}
else if(i==(fuhao.size()-1))
{
int d3=Integer.valueOf(num.get(i-1));
String s=timu.substring(0,d3)+num.get(i/2+1)+"*"+num.get(i/2);
chongti.add(s);
}
else
{
int d1=Integer.valueOf(num.get(i-1));
int d2=Integer.valueOf(num.get(i));
int d3=Integer.valueOf(num.get(i+1));
String s=timu.substring(0,d1)+num.get(i/2+1)+"*"+num.get(i/2)+timu.substring(d3, timu.length());
}
}
}
i++;
}
}
return chongti;
}
}
回归测试:
由于我们重构了计算答案这部分的代码,所以对这部分进行了单元测试。
覆盖率测试:
效能分析:
5.程序运行
乘方计算
分数计算
6.小结感受
经过两周的结对编程,结对编程在一定程度上是能达到1+1>2的效果,特别是在编写代码的过程中,一个功能如何实现或者bug的修改的时候,有时候自己做的时候容易陷入一个死胡同(有时候眼瞎,明显的错误都看不见),结对编程的时候就不一样了,两个人找起bug来速度明显快多了。而且在代码重构时,每个人对于功能的实现理解和想法都不同,所以两个人一起想可以互相弥补双方的不足,互相学习。
码云提交截图:
7.PSP
PSP2.1 | 个人开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 30 |
· Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 10 | 30 |
Development | 开发 | 720 | 900 |
· Analysis | 需求分析 (包括学习新技术) | 60 | 90 |
· Design Spec | 生成设计文档 | 10 | 30 |
· Design Review | 设计复审 | 10 | 20 |
· Coding Standard | 代码规范 | 30 | 35 |
· Design | 具体设计 | 90 | 120 |
· Coding | 具体编码 | 400 | 365 |
· Code Review | 代码复审 | 60 | 120 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | 100 | 170 |
· | 测试报告 | 40 | 70 |
· | 计算工作量 | 20 | 30 |
· | 并提出过程改进计划 | 40 | 70 |