php tdd,用phpUnit入门TDD
用phpunit实战TDD系列
从一个银行账户开始
假设你已经 安装了phpunit.
我们从一个简单的银行账户的例子开始了解TDD(Test-Driven-Development)的思想。
在工程目录下建立两个目录, src和test,在src下建立文件 BankAccount.php,在test目录下建立文件BankAccountTest.php。
按照TDD的思想,我们先写测试,再写生产代码,因此BankAccount.php留空,我们先写BankAccountTest.php。
现在我们运行一下,看看结果。运行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(后面称之为源文件)中输入以下内容:
运行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运行以下,测试通过。
这时,我们的生产代码为:
value = $n;
}
public function value(){
return $this->value;
}
public function deposit($ammount) {
$this->value += $ammount;
}
}
?>
总结
虽然我们的代码并不多,但是每一步都写得很有信心,这就是TDD的好处。即使你对php的语法不是很有把握(比如我),也可以对自己的代码很有信心。
用TDD的方式写程序的另一个好处,就是编码之前不需要对单个模块进行仔细的设计,可以在写测试的时候进行设计。这样开发出来的模块既可以满足用户需要,也不会冗余。
后面将会介绍 phpunit 的更多用法。
以上就介绍了用phpUnit入门TDD,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。
php tdd,用phpUnit入门TDD相关推荐
- 都在说TDD开发,那到底TDD是什么?
想了半天,确实是记不得什么时候第一次听说TDD了,我这里是要拍(拍板砖的拍)这个所谓的TDD,所以也就懒得去找TDD到底是什么时候提出来的,虽然google一下可能在第一页就能有正确的命中(比如wik ...
- phpunit入门_PHPUnit入门
phpunit入门 This post is outdated – for a more up-to-date introduction, please see the recently publis ...
- TDD测试驱动开发入门实践
最近和团队同学们分享了测试驱动开发实践,把分享的内容同步在博客上,主要分为三个方面,简单介绍软件测试的发展历程,为什么需要使用TDD,TDD的在编码中的实践. 一,软件测试的发展历程 调试为主: 怎 ...
- Laravel 测试: PHPUnit 入门教程
介绍 PHPUnit 测试的基础知识,使用基本的 PHPUnit 断言和 Laravel 测试助手. 介绍 PHPUnit 是最古老和最著名的 PHP 单元测试包之一.它主要用于单元测试,这意味着可以 ...
- phpunit学习第一章
用phpUnit入门TDD 用phpunit实战TDD系列 从一个银行账户开始 假设你已经 安装了phpunit. 我们从一个简单的银行账户的例子开始了解TDD(Test-Driven-Develop ...
- 免费直播| TDD如何颠覆了我对开发的认知?
不知道大家最近是否感到,2019年"敏捷开发"四个字的出现频率越来越高.在我的认知里,有果必有因. 5月初甲骨文裁员事件是风暴的开始,很多一线大厂,强如华为.腾讯.美团都准备做两手 ...
- tdd 私有方法_使用TDD方法构建自定义的Twig过滤器
tdd 私有方法 Twig is a powerful, yet easy to master template engine. It is also my personal favorite as ...
- 「敏捷架构」核心实践:测试驱动开发(TDD)简介
测试驱动开发(TDD) 是一种渐进的开发方法,它结合了测试优先的开发,即在编写足够的产品代码以完成测试和重构之前编写测试.TDD的主要目标是什么?一个观点是TDD的目标是规范而不是验证(Martin, ...
- TDD可以驱动设计吗?
前段时间有不少朋友发文讨论TDD引起了比较热烈的反响.我学习和实践TDD有近一年时间了,也希望把自己对TDD的理解拿出来讨论分享.本文讲讨论TDD的精髓和盲区,并希望引导TDD的初学者正确认识&quo ...
最新文章
- C#事件的发送方和接收方(订阅方)
- oracle 查询过去一个星期的数据_过去一星期,最懂我的居然是一个表情包
- php 时间戳获取周几,PHP实现根据时间戳获取周几的方法,php戳获取周_PHP教程
- iscsi-分区类型
- web 错误 处理你的请求时出错_最强辅助Jmeter技术干货:JMeter中处理器的另类玩法...
- 学C++不得不看的一篇文章
- 2019基金产品布局必备数据
- 热释电传感器三个引脚_智能家居组件漫谈——人体传感器
- 在进行ISO14001认证审核之前,需要注意什么?
- idea2017破解教程
- android简单悬浮窗源码,Android 悬浮窗的实现源码
- adb无线连接控制android手机
- 关于SpringCloud,Spring容器重复初始化的问题
- VS2015打开编译VS2013工程时提示fatal error C1083: 无法打开包括文件: “afxwin.h”: No such file or directory
- CPU密集和IO密集
- Asset Store上常用的40个Unity插件汇总——进阶开发者必备Unity插件
- 【研究】移动办公趋势洞察系列之二:人工智能、智能硬件精彩纷呈,业务协同初心不变
- WORD排版技巧大全。
- 心有多大世界就有多大
- 冠捷飞利浦_飞利浦所有色相灯泡之间的区别
热门文章
- 计算机名人堂(历届图灵奖获得者)
- vue实现移动端在线预览pdf
- jqurey怎么写入html,html如何写入和创建功能到jquery
- 扦插技术图解_扦插葡萄生根最快方法(图解)
- 如何彻底关闭Wiindows10系统自带输入法
- Romax Nexus—机电一体传动系统设计与仿真平台
- Android Project Butter 黄油计划
- 闪迪u盘不能识别好办法_U盘不能识别怎么办 U盘无法识别的7种解决方法
- centos7 teamspeak3的搭建
- MoveIt教程[11]:Visualizing Collisions