用phpUnit入门TDD

用phpunit实战TDD系列php

从一个银行帐户开始

假设你已经 安装了phpunit.html

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

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

按照TDD的思想,咱们先写测试,再写生产代码,所以BankAccount.php留空,咱们先写BankAccountTest.phpbootstrap

<?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一个文件,因此执行this

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 的更多用法。

相关文章
相关标签/搜索