学习PHP的过程很美好,感谢公司导师教了我很多在学校接触不到的东西。也让我接触到了TDD。
下面我就写一下自己对TDD的一点理解。
TDD即测试驱动开发(Test-Driven Development),采用测试的思想编写程序,通俗一点就是首先为自己的程序写测试,再一步步完成代码。不过,测试只能证明程序有错误,不能证明程序没错误。通过TDD我们在开发的过程中只用考虑一点就是把测试跑过。当所有测试都可以跑过,我们就有理由相信程序是正确的。
附上两个百科对TDD的理解:wiki对于TDD的缺点写的很好,值得思考。
TDD维基百科 TDD百度百科
当然在实现程序的过程中,我们也要一步步的完善我们的测试。以保证程序的健壮,后面我会提到这一点。
--------------------------------------------------------我-乃-分-割-线-----------------------------------------------------
程序要实现的功能是简单的四则运算(+,-,*,/)以及对括号的正确理解。
第一步写测试:assert(2 == Calc(1+1));
此时Calc函数中是没有内容的,所以这个测试不会通过。那么接下来就是补充Calc()让测试跑过。关于测试我先说这么多,下面主要谈实现的过程。
要实现四则运算,我们要考虑运用一种方法去吧表达式变的让计算机好理解并能够快速处理。就是把我们平时习惯的中缀表达式转换为前缀表达式或者后缀表达式。基于栈的程序语言更倾向于后缀表达,应该是跟栈的FILO性质有关。
我也是使用后缀表达式也叫逆波兰式。贴出程序:
<?php function OperationToRPN($operation)//将四则运算转化为后缀表示法 { $_Priority = array( '(' => 0,')' => 0, '+' => 1,'-' => 1, '*' => 2,'/' => 2);//定义优先级 $arr = str_split($operation);//把字符串转储到数组中 $temp = $data = $operator = array(); $n1 = count($arr);//标识符,循环的次数 for ($i=0; $i < $n1; $i++) { //判断括号是否匹配 $ch = $arr[$i]; switch ($ch) { case '(': array_push($temp, '('); break; case ')': if(empty($temp)||array_pop($temp) != '('){ return "The () is mismatching !"; } elseif (!empty($temp)) { return "The () is mismatching !"; } } } for ($i=0; $i < $n1; $i++) { $item = array_shift($arr);//依次取值 if(preg_match("/^[0-9]+/", $item)) {//匹配数字 array_push($data, $item); } elseif(($item == '+')||($item == '-')||($item == '*')||($item == '/')||($item == '(')||($item == ')')){ if((0 == $i)&&(($item == '*')||($item == '/')||($item == ')'))){ return('The */) is not allowed appear the first !');break; } if('('== $item){ array_push($operator, $item); } elseif(')'==$item) {//遇到‘)’,把栈中的元素弹出直到‘(’ while($operator[count($operator)-1] != '('){ $tem = array_pop($operator); array_push($data, $tem); } array_pop($operator); } else { if(0 == count($operator)){ array_push($operator,$item); } elseif($_Priority[$item] > $_Priority[$operator[count($operator)-1]]) { //判断操作符优先级 array_push($operator,$item); } else{ $tem = array_pop($operator); array_push($data,$tem); array_push($operator,$item); } } } else{ return('The parameter is illegal!');break; } } $n2 = count($operator); for($j = 0;$j < $n2;$j++) { //转换为逆波兰式 $tem = array_pop($operator); array_push($data, $tem); } $stack = implode('', $data); return OperationJsonData($stack); } function OperationJsonData($stack)//通过后缀格式进行计算 { $data = array(); $arr = str_split($stack);//把字符串转储到数组中 foreach ($arr as $char) { if(preg_match("/^[0-9]+/", $char)){ array_push($data, $char); } else { $b = array_pop($data); $a = array_pop($data); array_push($data, calc($a,$b,$char)); } } return end($data); } function calc($a,$b,$op) { switch ($op) { case '+': return $a + $b ; break; case '-': return $a - $b ; break; case '*': return $a * $b ; break; case '/': if(0 == $b){ return('Error!Division by zero!');break; } return $a / $b ; break; default: return('The Operator is illegal!');break; } } //test $fp = fopen(".....jsontest.txt", "r"); $n = 0; while (!feof($fp)) { $string = fgets($fp); $array[] = json_decode($string,true); assert($array[$n]["result"] == OperationToRPN($array[$n]["operation"])); $n++; echo "No.$n is ok</br>"; } fclose($fp); ?>
贴出jsontest.txt的内容:
{"operation":"1+1", "result":"2"} {"operation":"1*1", "result":"1"} {"operation":"1+2*3", "result":"7"} {"operation":"(1+2)*3", "result":"9"} {"operation":"1/0", "result":"Error!Division by zero!"} {"operation":"*1+2", "result":"The */) is not allowed appear the first !"} {"operation":"(1+2)*3)","result":"The () is mismatching !"}
至于怎么转换为后缀表达式我就不在叙述,说几个问题:
一、大家看主程序后面的测试,意思就是把要测试的数据以json格式传入程序,免得在主程序中出现那么多的assert语句。
在程序的开发过程中,尽量避免有重复的代码去实现相似的功能,也要避免冗余的代码,就是那些根本不可能被执行的代码。
二、随着开发的深入,要随时的补充测试。我现在的测试也不是最具代表性的,欢迎大牛们指点。当我完成基本的运算代码后,我加上了第5行测试,结果没有通过。就是当时的程序没有判断除数为0的情况。那接下来就是重构程序,直到可以跑通测试。所以就有了程序中判断除数的环节。
同样,第6行测试和第7行测试的加入也暴漏了程序其他方面的漏洞,随着测试覆盖面的增加,更多程序的漏洞就暴漏了出来,你要做的就是重构(Refactoring),直到测试通过。也就是说,你的测试覆盖的越全面,你的程序也越健壮。
三、注意尽量把输入输出从程序中剥离出来。这样,当你想改变输入的时候就避免了改源代码而有可能带来的风险。
四、程序到现在并不健壮。还需重构,这也是我接下来的任务。等到自认为满意了就告诉大家。
欢迎各位大牛指点!