Getting Started with TDD with phpUnit

WBOY
Release: 2016-08-08 09:29:49
Original
1419 people have browsed it

Using phpunit to practice TDD series

Start with a bank account

Assume you have phpunit installed.

We start with a simple bank account example to understand the idea of ​​TDD (Test-Driven-Development).

Create two directories in the project directory, src and test, create the file BankAccount.php under src, and create the file BankAccountTest.php under the test directory.

According to the idea of ​​TDD, we write the test first and then write the production code, so BankAccount.php is left blank and we write BankAccountTest.php first.

<code><?php
class BankAccountTest extends PHPUnit_Framework_TestCase
{
}
?></code>
Copy after login

Now let’s run it and see the results. The command line to run phpunit is as follows:

<code>phpunit --bootstrap src/BankAccount.php test/BankAccountTest.php</code>
Copy after login

--bootstrap src/BankAccount.php means to load src/BankAccount.php before running the test code. The test code to be run is test/BankAccountTest.php.

If you do not specify a specific test file and only provide a directory, phpunit will run all files in the directory whose file names match *Test.php. Because there is only one file BankAccountTest.php in the test directory, so execute

<code>phpunit --bootstrap src/BankAccount.php test</code>
Copy after login

You will get the same result.

<code>There was 1 failure:

1) Warning
No tests found in class "BankAccountTest".

FAILURES!
Tests: 1, Assertions: 0, Failures: 1.</code>
Copy after login

A warning error since there aren't any tests.

Account instantiation

Let’s add a test below. Note that TDD is a design method that can help you design the functionality of a module from the bottom up. When we write tests, we must start from the user's perspective. If a user uses our BankAccount class, what does he do first? It must be a new instance of BankAccount. So our first test is the test of instantiation.

<code>public function testNewAccount(){
    $account1 = new BankAccount();
}</code>
Copy after login

Running phpunit failed as expected.

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

No definition of the BankAccount class was found. Next we will write the production code. Make the test pass. Enter the following content in src/BankAccount.php (hereinafter referred to as the source file):

<code><?php
class BankAccount {
}
?></code>
Copy after login

Run phpunit and the test passes.

<code>OK (1 test, 0 assertions)</code>
Copy after login

Next, we need to add tests to make the tests fail. If you create a new account, the balance of the account should be 0. So we added an assert statement:

<code>public function testNewAccount(){
    $account1 = new BankAccount();
    $this->assertEquals(0, $account1->value());
}</code>
Copy after login

Note that value() is a member function of BankAccount. Of course, this function has not been defined yet. As users, we hope BankAccount provides this function.

Run phpunit, the results are as follows:

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

The result tells us that BankAccount does not have the value() member function. Add production code:

<code>class BankAccount {
    public function value(){
        return 0;
    }
}</code>
Copy after login

Why should value() return 0 directly? Because the test code expects value() to return 0. The principle of TDD is not to write redundant production code, just enough to allow the test to pass.

Account deposit and withdrawal

After running phpunit and passing, we first assume that the instantiation of BankAccount has met the requirements. Next, how does the user want to use BankAccount? You definitely want to deposit money into it. Well, I hope BankAccount has a deposit function. By calling this function, you can increase the account balance. So we add the next test.

<code>public function testDeposit(){
    $account = new BankAccount();
    $account->deposit(10);
    $this->assertEquals(10, $account->value());
}</code>
Copy after login

The initial balance of the account is 0. If we deposit 10 yuan into it, the account balance should of course be 10. Running phpunit, the test fails because the deposit function is not defined yet:

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

Next add the deposit function to the source file:

<code>public function deposit($ammount) {
}</code>
Copy after login

Run phpunit again and get the following results:

<code>1) BankAccountTest::testDeposit
Failed asserting that 0 matches expected 10.</code>
Copy after login

At this time, because we did not operate the account balance in the deposit function, the initial value of the balance is 0, and it is still 0 after the deposit function is executed, which is not the behavior expected by the user. We should add the amount deposited by the user to the balance.

In order to operate the balance, the balance should be a member variable of BankAccount. This variable is not allowed to be changed by the outside world, so it is defined as a private variable. Next we add the private variable $value to the production code, then the value function should return the value of $value.

<code>class BankAccount {
    private $value;
    
    public function value(){
        return $this->value;
    }

    public function deposit($ammount) {
        $this->value = 10;
    }
}</code>
Copy after login

Run phpunit and the test passes. Next, we thought, what else do users need? Yes, withdraw money. When withdrawing money, this value is deducted from the account balance. If you pass a negative number to the deposit function, it is equivalent to withdrawing money.
So we add two lines of code to the testDeposit function of the test code.

<code>$account->deposit(-5);
$this->assertEquals(5, $account->value());</code>
Copy after login

Run phpunit again and the test failed.

<code>1) BankAccountTest::testDeposit
Failed asserting that 10 matches expected 5.</code>
Copy after login

This is because in the production code we simply set $value to the result of 10. Improve production code.

<code>public function deposit($ammount) {
    $this->value += $ammount;
}</code>
Copy after login

Run phpunit again and the test passes.

New constructor

Next, it occurred to me that the user might need a different constructor that could pass in a value as the account balance when creating the BankAccount object. So we add this instantiation test in testNewAccount.

<code>public function testNewAccount(){
    $account1 = new BankAccount();
    $this->assertEquals(0, $account1->value());
    $account2 = new BankAccount(10);
    $this->assertEquals(10, $account2->value());
}</code>
Copy after login

Run phpunit, the result is:

<code>1) BankAccountTest::testNewAccount
Failed asserting that null matches expected 10.</code>
Copy after login

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

<code>public function __construct($n){
    $this->value = $n;
}</code>
Copy after login

再运行测试:

<code>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</code>
Copy after login

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

<code>public function __construct() {
    $this->value = 0;
}</code>
Copy after login

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

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

<code>public function __construct($n = 0){
    $this->value = $n;
}</code>
Copy after login

这样调用 new BankAccount()时,相当于传递了0给构造函数,满足了需求。
phpunit运行以下,测试通过。

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

<code><?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;
    }
}
?></code>
Copy after login

总结

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

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

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

以上就介绍了用phpUnit入门TDD,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template