用phpUnit入门TDD

用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:52) 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 0public 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学习第一章相关推荐

  1. QT学习 第一章:基本对话框--利用Qt Designer设计多个UI界面

    QT学习 第一章:基本对话框--利用Qt Designer设计多个UI界面 效果截图: 创建上文件夹Designers,使用Designer设计三个UI界面: First.ui Second.ui T ...

  2. 逻辑学学习:第一章:导论

    逻辑学学习:第一章:导论 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 开始学习逻辑学,教材为<<普通逻辑学>>,作者杨树森, ...

  3. Intel汇编语言程序设计学习-第一章 基本概念

    第一章基本概念 1.1  简单介绍 本书着重讲述MS-Windows平台上IA-32(Intel Architecture 32bit,英特尔32位体系架构)兼容微处理器的汇编语言程序设计,可以使用I ...

  4. python爬虫学习第一章

    <!DOCTYPE html> python爬虫第一章 python网络爬虫的学习 什么是网络爬虫 按照特定需求,从互联网中搜索有用信息网页进行过滤,就叫网络爬虫. 网络爬虫算法 当浏览信 ...

  5. 图解Http学习第一章

    今天正好闲来无事,顺便学习一波Http协议,感觉网络方面还是很重要的. 之前看过这本书,但是没总结也忘得差不多了,现在重新总结一遍,顺便当复习. 常规的Web访问流程: 这个就是比较基本的访问流程,不 ...

  6. 心向天蓝,Python学习第一章之列表一

    心向天蓝,Python学习第一程之列表一 下面这篇是关于列表的详细操作和介绍: 列表:是由一系列按特定顺序排列的元素组成. 在列表里,你可以加入任何你想加入的元素,可以是字母表里的任何元素,也可以是数 ...

  7. 《浅谈Cache Memory》 学习-第一章

    序 近些年,我在阅读一些和处理器相关的论文与书籍,有很多些体会,留下了若干文字.其中还是有一片领域,我一直不愿意书写,这片领域是处理器系统中的Cache Memory.我最后决定能够写下一段文字,不仅 ...

  8. javascript忍者秘籍(第二版)翻译学习 第一章 JavaScript无处不在

    前言废话(立flag) <javascript忍者秘籍第二版>这本书据说是JQUERY之父写的,emmm,肯定值得我这种菜狗来学习膜拜,所以打算花时间来把这本书通读记录一便,因为是看的英文 ...

  9. spring in action学习-第一章 spring之旅

    首先我先吐槽一下这本书的封面图,我能理解成一个包着头巾的男人举着个水壶昂首挺胸,最后给你个眼神....开玩笑的这幅插图是约旦西南部卡拉克省的居民,那里的山顶有座城堡,对死海和平原有极佳的视野,这幅图出 ...

最新文章

  1. 如何启用计算机超级账户,win7如何启用超级管理员账户 win7启用超级管理员账户方法介绍...
  2. 2021年广东赛区线上比赛高校组合点-五邑大学
  3. 搜索引擎爬虫蜘蛛的UserAgent收集
  4. 刷题中,效率好低,哈哈哈 -----1.Two Sum
  5. 服务器存储系统技术方案,服务器存储技术方案.pdf
  6. hdu 3652 B-number 数位dp
  7. 免费开源的thinkphp办公管理系统
  8. 95-860-045-源码-定时器-InternalTimerService
  9. 极品菜系列3 [图]
  10. windows应用程序签名
  11. pr如何处理音效_在pr中怎么让声音变的好听?pr怎么让说话的声音更好听?
  12. python中模块下载方法(conda+pip)
  13. 服务器电脑cpu性能排行,服务器cpu性能排行,小编教你服务器cpu性能排行
  14. ai条码插件免安装_Illustrator条形码插件
  15. django基础(四)详解Views视图层
  16. java矩形排样_优化排样(板材矩形一刀切算法)
  17. WPF学习之深入浅出话属性
  18. 把超星阅览器的文件转换为PDF文件
  19. 我的计算机光盘无法显示,为何电脑上插上光盘显示不出来
  20. 最完整的台达PLC培训教程(沈阳工大)学习笔记1

热门文章

  1. 椭圆形印章核心算法浅析及使用GDI+绘制椭圆印章的方法
  2. 硬盘数据丢失怎么恢复?分享固态硬盘恢复数据的4个方法
  3. 计算机主机电源怎么换,电脑主机不会拆装更换电源怎么办?详细图文来教你,看完你就会了...
  4. 2020主流室内定位技术对比
  5. 房屋租赁管理系统API
  6. SAP库存--历史库存相关数据,以及库存变化对应表的数据变化,可以用于库龄分析报表逻辑设计。
  7. 通过命令行操作iOS模拟器
  8. PowerMill 2018三五轴编程+后处理制作视频教程
  9. 计算机硬盘怎么看坏了,硬盘坏了怎么识别【如何修理】
  10. iOS 获取手机的ip地址 并传给后台(三步搞定)