用phpunit实战TDD系列

从一个银行账户开始

假设你已经 安装了phpunit.

我们从一个简单的银行账户的例子开始了解TDD(Test-Driven-Development)的思想。

在工程目录下建立两个目录, srctest,在src下建立文件 BankAccount.php,在test目录下建立文件BankAccountTest.php

按照TDD的思想,我们先写测试,再写生产代码,因此BankAccount.php留空,我们先写BankAccountTest.php

<?php
class BankAccountTest extends PHPUnit_Framework_TestCase
{
}
?>

现在我们运行一下,看看结果。运行phpunit的命令行如下:

phpunit --bootstrap src/BankAccount.php test/BankAccountTest.php

--bootstrap src/BankAccount.php是说在运行测试代码之前先加载 src/BankAccount.php,要运行的测试代码是test/BankAccountTest.php

如果不指定具体的测试文件,只给出目录,phpunit则会运行目录下所有文件名匹配 *Test.php 的文件。因为test目录下只有BankAccountTest.php一个文件,所以执行

phpunit --bootstrap src/BankAccount.php test

会得到一样的结果。

There was 1 failure:

1) Warning
No tests found in class "BankAccountTest". FAILURES!
Tests: 1, Assertions: 0, Failures: 1.

一个警告错误,因为没有任何测试。

账户实例化

下面我们添加一个测试。注意,TDD是一种设计方法,可以帮助你自底向上地设计一个模块的功能。我们写测试的时候,要从用户的角度出发。如果用户使用我们的BankAccount类,他首先做什么事呢?一定是新建一个BankAccount的实例。那么我们第一个测试就是对于 实例化 的测试。

public function testNewAccount(){
$account1 = new BankAccount();
}

运行phpunit,意料之中地失败。

PHP Fatal error:  Class 'BankAccount' not found in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5

没有发现BankAccount类的定义,下面我们就要写生产代码。使测试通过。在src/BankAccount.php(后面称之为源文件)中输入以下内容:

<?php
class BankAccount {
}
?>

运行phpunit,测试通过。

OK (1 test, 0 assertions)

接下来,我们要增加测试,使得测试失败。如果新建一个账户,账户的余额应该是0。于是我们添加了一个assert语句:

public function testNewAccount(){
$account1 = new BankAccount();
$this->assertEquals(0, $account1->value());
}

注意value()BankAccount的一个成员函数,当然这个函数还没有定义,作为使用者我们希望BankAccount提供这个函数。

运行phpunit,结果如下:

PHP Fatal error:  Call to undefined method BankAccount::value() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 6

结果告诉我们BankAccount并没有value()这个成员函数。添加生产代码:

class BankAccount {
public function value(){
return 0;
}
}

为什么要让value()直接返回0,因为测试代码中希望value()返回0。TDD的原则就是不写多余的生产代码,刚好让测试通过即可。

账户的存取

运行phpunit通过后,我们先假设BankAccount的实例化已经满足要求了,接下来,用户希望怎么使用BankAccount呢?一定希望往里面存钱,嗯,希望BankAccount有一个deposit函数,通过调用该函数,可以增加账户余额。于是我们增加下一个测试。

public function testDeposit(){
$account = new BankAccount();
$account->deposit(10);
$this->assertEquals(10, $account->value());
}

账户初始余额是0,我们往里面存10元,其账户余额当然应该为10。运行phpunit,测试失败,因为deposit函数还没有定义:

.PHP Fatal error:  Call to undefined method BankAccount::deposit() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 11

接下来在源文件中增加deposit函数:

public function deposit($ammount) {
}

再运行phpunit,得如下结果:

1) BankAccountTest::testDeposit
Failed asserting that 0 matches expected 10.

这时因为我们在deposit函数中并没有操作账户余额,余额初始值为0,deposit函数执行之后依然是0,不是用户期望的行为。我们应该往余额上增加用户存入的数值。

为了操作余额,余额应该是BankAccount的一个成员变量。这个变量不允许外界随便更改,因此定义为私有变量。下面我们在生产代码中加入私有变量$value,那么value函数应该返回$value的值。

class BankAccount {
private $value; public function value(){
return $this->value;
} public function deposit($ammount) {
$this->value = 10;
}
}

运行 phpunit,测试通过。接下来,我们想,用户还需要什么?对,取钱。当取钱时,账户余额要扣除这个值。如果给 deposit函数传递负数,就相当于取钱了。

于是我们在测试代码的testDeposit函数中增加两行代码。

$account->deposit(-5);
$this->assertEquals(5, $account->value());

再运行 phpunit,测试失败了。

1) BankAccountTest::testDeposit
Failed asserting that 10 matches expected 5.

这时因为在生产代码中我们简单地把$value设成10的结果。改进生产代码。

public function deposit($ammount) {
$this->value += $ammount;
}

再运行phpunit,测试通过。

新的构造函数

接下来,我想到,用户可能需要一个不同的构造函数,当创建BankAccount对象时,可以传入一个值作为账户余额。于是我们在testNewAccount增加这种实例化的测试。

public function testNewAccount(){
$account1 = new BankAccount();
$this->assertEquals(0, $account1->value());
$account2 = new BankAccount(10);
$this->assertEquals(10, $account2->value());
}

运行phpunit,结果为:

1) BankAccountTest::testNewAccount
Failed asserting that null matches expected 10.

这时因为BankAccount没有带参数的构造函数,因此new BankAccount(10)会返回一个空对象,空对象的value()函数自然返回的也是null。为了通过测试,我们在生产代码中增加带参数的构造函数。

public function __construct($n){
$this->value = $n;
}

再运行测试:

1) BankAccountTest::testNewAccount
Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5
/home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:5 2) BankAccountTest::testDeposit
Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 12 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5
/home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:12

两个调用new BankAccount()的地方都报告了错误,增加了带参数的构造函数,不带参数的构造函数又不行了。从c++/java过渡来的同学马上想到增加一个默认的构造函数:

public function __construct() {
$this->value = 0;
}

但这样是不行的,因为php不支持函数重载,所以不能有多个构造函数。

怎么办?对了,我们可以为参数增加默认值。修改构造函数为:

public function __construct($n = 0){
$this->value = $n;
}

这样调用 new BankAccount()时,相当于传递了0给构造函数,满足了需求。

phpunit运行以下,测试通过。

这时,我们的生产代码为:

<?php
class BankAccount {
private $value; // default to 0 public function __construct($n = 0){
$this->value = $n;
} public function value(){
return $this->value;
} public function deposit($ammount) {
$this->value += $ammount;
}
}
?>

总结

虽然我们的代码并不多,但是每一步都写得很有信心,这就是TDD的好处。即使你对php的语法不是很有把握(比如我),也可以对自己的代码很有信心。

用TDD的方式写程序的另一个好处,就是编码之前不需要对单个模块进行仔细的设计,可以在写测试的时候进行设计。这样开发出来的模块既可以满足用户需要,也不会冗余。

后面将会介绍 phpunit 的更多用法。

用phpUnit入门TDD的更多相关文章

  1. phpunit 入门

    话说,使用phpunit还是处于好奇之心,为什么以前一直没使用呢?主要是嫌麻烦,小项目,前后端都是自己写,几个人就完成的工作,没必要弄的那么麻烦.好了,废话不多说,让我们一起来感受下phpunit吧! ...

  2. PHPUnit入门

    PHPUnit是PHP语言的单元测试框架.工具,xunit单元测试工具系列成员之一,可以单独运行在Linux或windows系统下面,也可以集成到zend studio等IDE工具中. 工具下载:ht ...

  3. 50分钟学会Laravel 50个小技巧

    50分钟学会Laravel 50个小技巧 时间 2015-12-09 17:13:45  Yuansir-web菜鸟 原文  http://www.yuansir-web.com/2015/12/09 ...

  4. 50分钟学会Laravel 50个小技巧(基于laravel5.2,仅供参考)

    转载请注明:转载自 Yuansir-web菜鸟 | LAMP学习笔记 本文链接地址: 50分钟学会Laravel 50个小技巧 原文链接:< 50 Laravel Tricks in 50 Mi ...

  5. PHP Unit资料收集

    ThinkPHP Unit https://github.com/gaoermai/ThinkPHPUnit PHPUnit入门http://blog.csdn.net/fly_heart_yuan/ ...

  6. phpunit测试学习 1:一点简单的扼要有用的东西的总结 一点入门认识

    16:45 2015/12/8phpunit测试学习 1:一点简单的扼要有用的东西的总结  一点入门认识 具体的入门安装和入门实践请参照文中的推荐博客或网上其他博客推荐博客,我感觉这几篇博客写得很不错 ...

  7. 测试驱动开发(TDD)及测试框架Mocha.js入门学习

    组里马上要转变开发模式,由传统的开发模式(Developer开发,QA测试),转变为尝试TDD(Test-driven development,测试驱动开发)的开发模型.由此将不存在QA的角色,或者仅 ...

  8. php单元测试入门教程phpunit详解

    本文档提供了一些phpunit官方教程没有提到的信息,帮助初学者快速了解php单元测试,在phpunit官网提供了详细的中文教程,可选多种格式下载 phpunit官网地址:https://phpuni ...

  9. (转)TDD的iOS开发初步以及Kiwi使用入门

    本文转自“瞄神”博客 TDD的iOS开发初步以及Kiwi使用入门 测试驱动开发(Test Driven Development,以下简称TDD)是保证代码质量的不二法则,也是先进程序开发的共识.App ...

随机推荐

  1. Qt Lighthouse学习(二),就是QPA(Qt Platform Abstraction) 项目的名字

    上一次关注Qt Lighthouse是在6月初,可是现在都8月底了.时间真快... Lighthouse 是 QPA(Qt Platform Abstraction) 项目的名字,它使得将Qt移植到新 ...

  2. 从解决一个java.lang.NoSuchMethodError想到的

    今天在发布系统部署一个web app的时候,发现应用服务器(tomcat 7.0.26)不能正常启动,于是远程登陆到服务器上查看应用服务器的启动日志,在tomcat_home的logs/localho ...

  3. mysql 开发基础系列3

    日期类型 如果要用来表示年月日,通常用DATE 来表示. 如果要用来表示年月日时分秒,通常用DATETIME 表示. 如果只用来表示时分秒,通常用TIME 来表示. TIMESTAMP表示格式 :YY ...

  4. 【Python】使用python操作mysql数据库

    这是我之前使用mysql时用到的一些库及开发的工具,这里记录下,也方便我查阅. python版本: 2.7.13 mysql版本: 5.5.36 几个python库 1.mysql-connector ...

  5. matlab gradient 和 prctile

    介绍两个matlab小函数: 1.gradient 借用别人的例子:例:>> x=[6,9,3,4,0;5,4,1,2,5;6,7,7,8,0;7,8,9,10,0]x =     6  ...

  6. MT【161】韦恩图

    (清华2017.4.29标准学术能力测试25) 若$N$的三个子集$A,B,C$满足$|A\cap B|=|B\cap C|=|C\cap A|=1$,且$A\cap B\cap C=\varnoth ...

  7. 【BZOJ2118】墨墨的等式(最短路)

    [BZOJ2118]墨墨的等式(最短路) 题面 BZOJ 洛谷 题解 和跳楼机那题是一样的. 只不过走的方式从\(3\)种变成了\(n\)种而已,其他的根本没有区别了. #include<ios ...

  8. MacBook设置终端颜色,补全忽略大小写,设置命令别名alias,设置vim,设置显示git分支

    1.启用终端颜色 修改配置文件 $ vim .bash_profile #enables colorin the terminal bash shell export export CLICOLOR= ...

  9. 【最大流/二分图匹配】【网络流24题】【P3254】 圆桌问题

    Description 假设有来自m 个不同单位的代表参加一次国际会议.每个单位的代表数分别为ri (i =1,2,--,m). 会议餐厅共有n 张餐桌,每张餐桌可容纳ci (i =1,2,--,n) ...

  10. 【整体二分】【P3527】 [POI2011]MET-Meteors

    Description 有 n 个国家,总共占有一个环,环被分成了 m 段,已知有 k 次流星雨会落在这个环上的一些位置.再给出每个国家目标收集多少流星,对每个国家求出第几次流星雨后可以满足这个国家的 ...