用phpUnit入门TDD

从一个银行账户开始
假设你已经 安装了phpunit.
 
我们从一个简单的银行账户的例子开始了解TDD(Test-Driven-Development)的思想。
 
在工程目录下建立两个目录, src和test,在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 的更多用法。

时间: 2024-10-29 17:04:51

用phpUnit入门TDD的相关文章

TDD的iOS开发初步以及Kiwi使用入门

测试驱动开发(Test Driven Development,以下简称TDD)是保证代码质量的不二法则,也是先进程序开发的共识.Apple一直致力于在iOS开发中集成更加方便和可用的测试,在Xcode 5中,新的IDE和SDK引入了XCTest来替代原来的SenTestingKit,并且取消了新建工程时的"包括单元测试"的可选项(同样待遇的还有使用ARC的可选项).新工程将自动包含测试的target,并且相关框架也搭建完毕,可以说测试终于摆脱了iOS开发中"二等公民"

PHP开发工具Zend Studio7入门使用教程

对于PHP网站开发者来说,选择一款好用的PHP开发工具是很重要的,目前比较有名的PHP开发工具有zend studio.Eclipse.Editplus等,当初我刚入门学习PHP的时候,使用的PHP开发工具是Editplus,后来才慢慢过渡到更多使用zend studio,相对来说Editplus是一个轻量级的PHP开发工具,适合PHP入门学习者使用,zend studio功能更强大,但你要说哪个是最好的PHP开发工具,那可真说不清道不明了,只有最合适,没有最好,对于有一定PHP基础知识的童鞋我

JUnit和单元测试入门简介

JUnit和单元测试入门简介 1.几个相关的概念 白盒测试--把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人员是公开的. 回归测试--软件或环境的修复或更正后的"再测试",自动测试工具对这类测试尤其有用. 单元测试--是最小粒度的测试,以测试某个功能或代码块.一般由程序员来做,因为它需要知道内部程序设计和编码的细节. JUnit --是一个开发源代码的Java测试框架,用于编写和运行可重复的测试.他是用于单元测试框架体系xUnit的一个实例(用于java语言).主要

用NUnit2.1简单实现.net的测试驱动开发(TDD)

用NUnit2.1简单实现.net的测试驱动开发(TDD)下面的例子很简单,就是实现两个整数的四则运算,TDD提倡测试优先,即先写测试用例,再写运行代码,刚下了个NUnit2.1,迫不及待的试了试--1最初的测试用例using System;using NUnit.Framework;namespace netshop{ /// <summary> /// 四则运算TestCls测试用例 /// Edit by spgoal /// </summary> [TestFixture]

JUnit入门及应用

1.相关概念 Ø JUnit:是一个开发源代码的Java测试框架,用于编写和运行可重复的测试.它是用于单元测试框架体系xUnit的一个实例(用于java语言).主要用于白盒测试,回归测试. Ø 白盒测试:把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人 员是公开的. Ø 回归测试:软件或环境的修复或更正后的再测试,自动测试工具对这类测试尤其有用. Ø 单元测试:最小粒度的测试,以测试某个功能或代码块.一般由程序员来做,因为它需要知道内部程序设计和编码的细节. 2. 单元测试 2

零基础入门深度学习(3) - 神经网络和反向传播算法

   神经元   神经元和感知器本质上是一样的,只不过我们说感知器的时候,它的激活函数是阶跃函数:而当我们说神经元时,激活函数往往选择为sigmoid函数或tanh函数.如下图所示:       sigmoid函数的定义如下:     将其带入前面的式子,得到     sigmoid函数是一个非线性函数,值域是(0,1).函数图像如下图所示     sigmoid函数的导数是:     可以看到,sigmoid函数的导数非常有趣,它可以用sigmoid函数自身来表示.这样,一旦计算出sigmoi

用Python进行行为驱动开发的入门教程_python

为驱动开发(Behavior-Driven Development,BDD)是一种卓越的开发模式.能帮助开发者养成日清日结的好习惯,从而避免甚至杜绝"最后一分钟"的情况出现,因此对提高代码质量是大有裨益的.其与Gherkin语法相结合的测试结构及设计形式,使得对团队的全部成员包括非技术人员都具有极好的易读性. 所有代码都必须进行测试,这意味着上线时把系统瑕疵降到最低甚至为零.这需要与完整的测试套件相配,从整体把控软件行为,使得检测与维护都能有序进行.这就是BDD的魅力所在,难道不心动吗

零基础入门深度学习(二):神经网络和反向传播算法

投稿:Intelligent Software Development 团队介绍:团队成员来自一线互联网公司,工作在架构设计与优化.工程方法研究与实践的最前线,曾参与搜索.互联网广告.共有云/私有云等大型产品的设计.开发和技术优化工作.目前主要专注在机器学习.微服务架构设计.虚拟化/容器化.持续交付/DevOps等领域,希望通过先进技术和工程方法最大化提升软件和服务的竞争力.   在上一篇文章<零基础入门深度学习:感应器.线性单元和梯度下降>中,我们已经掌握了机器学习的基本套路,对模型.目标函

Java新手入门教程:新手必须掌握的30条Java基本概念

  Java新手必看教程是什么?当然是绿茶小编带来的Java入门需掌握的30个基本概念啦,掌握了这些概念对于学习Java大大有利,正在学习Java编程的同学们快来看看吧. 1.OOP中唯一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构 是怎样的,他只关系能否给你提供电就行了,也就是只要知道can or not而不是how and why.所有的程序是由一定的属性和行为对象组成的,不同的对象的访问通过函数调用来完成,对象间所有的交流都是通过方法调用,通过对封装对象数据,很大 限度上