Laravel 测试: PHPUnit 入门教程

file

介绍 PHPUnit 测试的基础知识,使用基本的 PHPUnit 断言和 Laravel 测试助手。php

介绍

PHPUnit 是最古老和最著名的 PHP 单元测试包之一。它主要用于单元测试,这意味着能够用尽量小的组件测试代码,可是它也很是灵活,能够用于不少不只仅是单元测试。html

PHPUnit 包含许多简单和灵活的断言容许您轻松地测试代码,当您测试特定的组件时,这些断言很是有效。可是,它确实意味着测试更高级的代码(如控制器和表单提交验证)可能会复杂得多。laravel

为了帮助开发人员更容易地进行开发, Laravel 框架  包含了一系列 应用程序测试帮助程序 ,容许您编写很是简单的 PHPUnit 测试来测试应用程序的复杂部分。git

本教程的目的是向您介绍 PHPUnit 测试的基础知识,使用默认 PHPUnit 断言和 Laravel 测试助手。这样作的目的是在本教程结束时,您能够自信地为应用程序编写基本测试。github

前提

本教程假设您已经熟悉 Laravel 并知道如何在应用程序目录中运行命令(例如 php artisan 命令)。咱们将建立几个基本的示例类来学习不一样的测试工具如何工做,所以建议您为本教程建立一个新的应用程序。web

若是已经安装了 Laravel ,则能够经过运行如下命令建立新的测试应用程序:数组

laravel new phpunit-tests

复制代码

或者,您能够直接使用 Composer 建立新应用程序:浏览器

composer create-project laravel/laravel --prefer-dist

复制代码

其余安装方法也能够在 Laravel 文档中找到。bash

建立一个新的测试

使用 PHPUnit 的第一步是建立一个新的测试类。测试类的约定是它们存储在应用程序目录的 ./tests/ 下。在这个文件夹中,每一个测试类都被命名为 <name>Test.php 。这种格式容许 PHPUnit 查找每一个测试类---它将忽略任何不以 Test.php 结尾的文件。服务器

在新的 Laravel 应用程序中,你会注意到 ./tests/ 目录中有两个文件:  ExampleTest.php 和 TestCase.php.  TestCase.php 文件是一个引导文件用于在咱们的测试中设置 Laravel 环境。这容许咱们在测试中使用 Laravel Facades 并为测试助手提供框架,咱们将在稍后介绍。 ExampleTest.php 是一个示例测试类,其中包含使用应用程序测试助手的基本测试用例-暂时忽略它。

要建立一个新的测试类,咱们能够手动建立一个新文件,或者运行由 Laravel 提供的 Artisan 命令 make:test

为了建立一个名为 BasicTest 的测试类,咱们只须要运行这个 artisan 命令:

php artisan make:test BasicTest

复制代码

Laravel 将建立一个以下所示的基本测试类:

<?php
class BasicTest extends TestCase {
    /** * 一个基本的测试示例。 * * @return void */
    public function testExample() {
        $this->assertTrue(true);
    }
}

复制代码

这里要注意的最重要的事情是 test 方法名称上的前缀,与 Test 类名后缀同样,这样 test 前缀告诉 PHPUnit 在测试时运行哪些方法。若是您忘记了 test 前缀,那么 PHPUnit 将忽略该方法。

在咱们第一次运行测试套件以前,有必要指出 Laravel 提供的默认 phpunit.xml 文件。 PHPUnit 在运行时会自动在当前目录中查找名为 phpunit.xml 或者 phpunit.xml.dist 的文件。您能够在此处配置测试的特定选项。

这个文件中有不少信息,可是如今最重要的部分是在 testsuite 目录定义:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit ... >

    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>

    ...
</phpunit>

复制代码

这将告诉 PHPUnit 运行时在 ./tests/ 目录中找到的测试,正如咱们以前所知,这是存储测试的约定。

如今咱们已经建立了一个基本测试,而且知道了 PHPUnit 配置,如今是第一次运行测试的时候了。

您能够经过运行如下 phpunit 命令来运行测试:

./vendor/bin/phpunit

复制代码

您应该看到与此相似的输出:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

..

Time: 103 ms, Memory: 12.75Mb

OK (2 tests, 3 assertions)

复制代码

如今咱们已经有了一个有效的 PHPUnit 设置,如今是时候开始编写一个基本测试了。

注意,它会统计2个测试和3个断言,由于 ExampleTest.php 文件包含了一个带有两个断言的测试。咱们的新基本测试包括一个单独的断言,该断言已经过。

写一个基础测试

为了帮助 PHPUnit 提供的基本断言,咱们将首先建立一个提供一些简单功能的基本类

在 ./app/ 目录中建立一个名为 Box.php 的新文件,并复制此示例类:

<?php
namespace App;

class Box {
    /** * @var array */
    protected $items = [];

    /** * 使用给定项构造框 * * @param array $items */
    public function __construct($items = []) {
        $this->items = $items;
    }

    /** * 检查指定的项目是否在框中。 * * @param string $item * @return bool */
    public function has($item) {
        return in_array($item, $this->items);
    }

    /** * 从框中移除项,若是框为空,则为 null 。 * * @return string */
    public function takeOne() {
        return array_shift($this->items);
    }

    /** * 从包含指定字母开头的框中检索全部项目。 * * @param string $letter * @return array */
    public function startsWith($letter) {
        return array_filter($this->items, function ($item) use ($letter) {
            return stripos($item, $letter) === 0;
        });
    }
}

复制代码

接下来, 打开你的 ./tests/BasicTest.php 类(咱们以前建立的类),并删除默认建立的 testExample 方法, 你应该留一个空类。

咱们如今将使用七个基本的 PHPUnit 断言来为咱们的 Box 类编写测试。这些断言是:

  • assertTrue()
  • assertFalse()
  • assertEquals()
  • assertNull()
  • assertContains()
  • assertCount()
  • assertEmpty()

assertTrue() 和 assertFalse()

assertTrue() 和 assertFalse() 容许你声明一个值等于 true 或 false 。这意味着它们很是适合测试返回布尔值的方法。在咱们的 Box 类中,咱们有一个名为 has($item) 的方法,当指定的项在 box 中或不在 box 中时,该方法返回对应返回 true 或 false .

要在 PHPUnit 中为此编写测试,咱们能够执行如下操做:

<?php
use App\Box;

class BasicTest extends TestCase {
    public function testHasItemInBox() {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }
}

复制代码

注意咱们如何只将一个参数传递给 assertTrue() 和 assertFalse() 方法,而且它是 has($item) 方法的输入.

若是您如今运行 ./vendor/bin/phpunit 命令,您会注意到输出包括:

OK (2 tests, 4 assertions)

复制代码

这意味着咱们的测试已经经过。

若是您将 assertFalse() 替换成 assertTrue() 并运行 phpunit 命令,输出将以下所示:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

F.

Time: 93 ms, Memory: 13.00Mb

There was 1 failure:

1) BasicTest::testHasItemInBox
Failed asserting that false is true.

./tests/BasicTest.php:12

FAILURES!
Tests: 2, Assertions: 4, Failures: 1.

复制代码

这告诉咱们第12行的断言未能断言 false 值是 true - 由于咱们将 assertFalse() 替换为 assertTrue()

将其交换回来,而后从新运行 PHPUnit 。测试应该再次经过,由于咱们已经修复了破损的测试。

assertEquals() 与 assertNull()

接下来,让咱们看看 assertEquals(), 以及 assertNull()

assertEquals() 用于比较变量实际值与预期值是否相等。咱们用它来检查 takeOne() 方法的返回值是否为 Box 内的当前值。当 Box 为空时,takeOne() 将返回 null,咱们亦可以使用 assertNull() 来进行检查。

与 assertTrue()assertFalse() 以及 assertNull() 不一样,assertEquals() 须要两个参数。第一个参数为 预期 值,第二个参数则为 实际 值。

可参照以下代码实现以上断言(assertions):

<?php
use App\Box;

class BasicTest extends TestCase {
    public function testHasItemInBox() {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }

    public function testTakeOneFromTheBox() {
        $box = new Box(['torch']);

        $this->assertEquals('torch', $box->takeOne());

        // 当前 Box 为空,应当为 Null
        $this->assertNull($box->takeOne());
    }
}

复制代码

运行 phpunit 命令,你应当看到以下输出:

OK (3 tests, 6 assertions)

复制代码

assertContains() 和 assertCount() 以及 assertEmpty()

终于,咱们有三个做用于数组有关的断言,咱们可以使用它们去检查 Box 类中的  startsWith($item) 方法。 assertContains() 断言传递进来的数组中包含指定值, assertCount() 断言数组的项数为指定数量,assertEmpty() 断言传递进来的数组为空。

让咱们来执行如下测试:

<?php
use App\Box;

class BasicTest extends TestCase {
    public function testHasItemInBox() {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }

    public function testTakeOneFromTheBox() {
        $box = new Box(['torch']);

        $this->assertEquals('torch', $box->takeOne());

        // Null,如今这个 box 是空的。
        $this->assertNull($box->takeOne());
    }

    public function testStartsWithALetter() {
        $box = new Box(['toy', 'torch', 'ball', 'cat', 'tissue']);

        $results = $box->startsWith('t');

        $this->assertCount(3, $results);
        $this->assertContains('toy', $results);
        $this->assertContains('torch', $results);
        $this->assertContains('tissue', $results);

        // 若是传递复数断言数组为空
        $this->assertEmpty($box->startsWith('s'));
    }
}

复制代码

保存并再一次运行你的测试:

OK (4 tests, 9 assertions)

复制代码

恭喜你,你刚刚使用七个基础的 PHPUnit 断言完成了对 Box 类的所有测试。经过这些简单的断言你可以作许多事,对于其余断言,大多数要更复杂,不过它们仍遵循以上使用规则。

测试你的程序

在你的程序里,对每一个组件进行单元测试在不少状况下都是有必要的,并且也应该成为你开发过程当中必不可少的一部分,但这并非你须要作的所有的测试。当你构建一个包含复杂视图、导航和表单的程序时,你一样想测试这些组件。这时,Laravel的测试助手可使这些测试像单元测试简单组件同样容易。

咱们以前查看在 ./tests/ 目录下的默认文件时跳过了 ./tests/ExampleTest.php 文件。 如今打开它,内容以下所示:

<?php
class ExampleTest extends TestCase {
    /** * 一个基本功能测试示例。 * * @return void */
    public function testBasicExample() {
        $this->visit('/')
             ->see('Laravel 5');
    }
}

复制代码

咱们能够看到这个测试示例很是简单。在不知道测试助手如何运做的状况下,咱们能够猜想它的意思以下:

  1. 当我访问/ (根目录)
  2. 我应该看到 'Laravel 5'

若是你打开你的web浏览器,访问咱们的程序(若是你没有启动你的web服务器,你能够运行 php artisan serve ),你应该能够在web根目录上看到屏幕上有“Laravel 5”的文本。 鉴于这个测试已经经过了PHPUnit,咱们能够很肯定地说咱们对这个测试示例改造是正确的。

这个测试确保了访问/路径,网页能够返回“'Laravel 5”的文本。一个如此简单的检查也许不表明什么,但若是你的网站上要显示关键信息,它就能够在一个别处的改动致使这个页面没法正常显示正确的信息时,防止你部署一个被损坏的程序。

visit()、see() 以及 dontSee()

如今尝试编写本身的测试,更进一步理解它吧。

首先,编辑 ./app/Http/routes.php ,增长一个新的路由。为了教程目的,咱们建立希腊字母定义的路由:

<?php
Route::get('/'function () {
    return view('welcome');
});

Route::get('/alpha'function () {
    return view('alpha');
});

复制代码

而后,建立视图文件 ./resources/views/alpha.blade.php,使用 Alpha 做为关键字,保存基本的HTML文件:

<!DOCTYPE html>
<html>
    <head>
        <title>Alpha</title>
    </head>
    <body>
        <p>This is the Alpha page.</p>
    </body>
</html>

复制代码

打开浏览器,输入网址: http://localhost:8000/beta,页面会显示出 "This is the Alpha page." 的内容。

如今咱们有了测试用到的模版文件,下一步,咱们经过运行命令 make:test 来建立一个新的测试文件:

php artisan make:test AlphaTest

复制代码

而后变成刚建立好的测试文件,按照框架提供的例子,测试 "alpha" 页面上没有包含 "beta" 。 咱们可使用方法 dontSee() ,它是 see() 的对应的反向方法。

下面代码是上面实现的简单例子:

<?php
class AlphaTest extends TestCase {
    public function testDisplaysAlpha() {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta');
    }
}

复制代码

保存并运行 PHPUnit (./vendor/bin/phpunit),测试代码应该会所有经过,你会看到像这样的测试状态内容显示:

OK (5 tests,12 assertions)

复制代码

开发前先写测试

对于测试来讲,测试驱动开发 (TDD) 是很是酷的方法,首先咱们先写测试。写完测试并执行它们,你会发现测试没经过,接下来 咱们编写知足测试的代码,再次执行测试,使测试经过。 接下来让咱们开始。

首先,创建一个 BetaTest 类使用 make:test artisan 命令:

php artisan make:test BetaTest

复制代码

接下来,更新测试用例以便检查 /beta 的路由 route 为「Beta」:

<?php
class BetaTest extends TestCase {
    public function testDisplaysBeta() {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha');
    }
}

复制代码

如今使用 ./vendor/bin/phpunit 命令来执行测试。结果是一个看起来简洁但很差的错误信息,以下:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

....F.

Time: 144 ms, Memory: 14.25Mb

There was 1 failure:

1) BetaTest::testDisplaysBeta
一个对 [http://localhost/beta] 的请求失败了。收到状态码 [404]。

...

FAILURES!
Tests: 6, Assertions: 13, Failures: 1.

复制代码

咱们如今须要建立这个不存在的路由。让咱们开始。

首先,编辑 ./app/Http/routes.php 文件来建立新的 /beta 路由:

<?php
Route::get('/', function () {
    return view('welcome');
});

Route::get('/alpha', function () {
    return view('alpha');
});

Route::get('/beta', function () {
    return view('beta');
});

复制代码

接下来,在 ./resources/views/beta.blade.php 下建立以下视图模版:

<!DOCTYPE html>
<html>
    <head>
        <title>Beta</title>
    </head>
    <body>
        <p>This is the Beta page.</p>
    </body>
</html>

复制代码

如今再一次执行 PHPUnit,结果应该再一次回到绿色。

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

......

Time: 142 ms, Memory: 14.00Mb

OK (6 tests, 15 assertions)

复制代码

这样咱们就经过在完成新的页面以前写测试的方式,对 测试驱动开发 进行了实践。

click() 和 seePageIs()

Laravel 也提供一个辅助函数 (click()) 容许测试点击页面中存在的链接 ,以及一个方法 (seePageIs()) 检查点击展现的结果页面。

让咱们使用这两个辅助函数去执行在 Alpha 和 Beta 页面的连接。

首先,咱们更新咱们的测试。打开 AlphaTest 类,咱们将添加一个新的测试方法,这将点击 「alpha」页面上的「Next」连接跳转到 「beta」页面。

新的测试代码以下:

<?php
class AlphaTest extends TestCase {
    public function testDisplaysAlpha() {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta');
    }

    public function testClickNextForBeta() {
        $this->visit('/alpha')
             ->click('Next')
             ->seePageIs('/beta');
    }
}

复制代码

注意到,在咱们新建的 testClickNextForBeta() 方法中,咱们并无检查每个页面的内容。 其余测试都成功的检查了两个页面的内容,因此这里咱们只关心点击 「Next」连接将发送到 /beta

你如今能够运行测试组件了,但就像预料的同样测试将不经过,由于咱们尚未更新咱们的 HTML。

接下来,咱们将更新 BetaTest 来作相似的事情:

<?php
class BetaTest extends TestCase {
    public function testDisplaysBeta() {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha');
    }

    public function testClickNextForAlpha() {
        $this->visit('/beta')
             ->click('Previous')
             ->seePageIs('/alpha');
    }
}

复制代码

接下来,咱们更新咱们的 HTML 模版。

./resources/views/alpha.blade.php

<!DOCTYPE html>
<html>
    <head>
        <title>Alpha</title>
    </head>
    <body>
        <p>This is the Alpha page.</p>
        <p><a href="/beta">Next</a></p>
    </body>
</html>

复制代码

./resources/views/beta.blade.php

<!DOCTYPE html>
<html>
    <head>
        <title>Beta</title>
    </head>
    <body>
        <p>This is the Beta page.</p>
        <p><a href="/alpha">Previous</a></p>
    </body>
</html>

复制代码

保存文件,再一次执行 PHPUnit:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

F....F..

Time: 175 ms, Memory: 14.00Mb

There were 2 failures:

1) AlphaTest::testDisplaysAlpha
Failed asserting that '<!DOCTYPE html> <html> <head> <title>Alpha</title> </head> <body> <p>This is the Alpha page.</p> <p><a href="/beta">Next</a></p> </body> </html> ' does not match PCRE pattern "/Beta/i".

2) BetaTest::testDisplaysBeta
Failed asserting that '<!DOCTYPE html> <html> <head> <title>Beta</title> </head> <body> <p>This is the Beta page.</p> <p><a href="/alpha">Previous</a></p> </body> </html> ' does not match PCRE pattern "/Alpha/i".

FAILURES!
Tests: 8, Assertions: 23, Failures: 2.

复制代码

然而测试失败了。若是你仔细观察咱们的新 HTML,你将注意到咱们分别有术语 beta 和 alpha 在 /alpha 和 /beta 页面。这意味着咱们须要稍微更改咱们的测试让它们与误报不匹配。

在每个 AlphaTest 和 BetaTest 类,更新 testDisplays* 方法去使用 dontSee('<page> page')。经过这种方式,这将仅仅匹配字符串而不是那个术语。

两个测试文件以下所示:

./tests/AlphaTest.php

<?php
class AlphaTest extends TestCase {
    public function testDisplaysAlpha() {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta page');
    }

    public function testClickNextForBeta() {
        $this->visit('/alpha')
             ->click('Next')
             ->seePageIs('/beta');
    }
}

复制代码

./tests/BetaTest.php

<?php
class BetaTest extends TestCase {
    public function testDisplaysBeta() {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha page');
    }

    public function testClickNextForAlpha() {
        $this->visit('/beta')
             ->click('Previous')
             ->seePageIs('/alpha');
    }
}

复制代码

再一次运行你的测试,全部的测试都应该经过了。咱们如今已经测试咱们全部的新文件,包括页面中的 Next/Previous 连接。

经过 Semaphore 对 PHPUnit 持续集成

经过 Semaphore设置 持续集成你能够自动执行你的测试。

这样每一次你进行 git push 提交代码的时候都会执行你的测试,而且 Semaphore 预装了全部最新的 PHP 版本

若是你尚未一个 Semaphore 帐户, 先去 注册一个免费的 Semaphore 帐户 。接下来须要作的是将它 添加到你的项目,并按照提示逐步去作来执行你的测试:

composer install --prefer-source
phpunit

复制代码

关于 PHP 持续集成 的更多信息,请参照 Semaphore 文档。

结语

你应该注意到本教程中的全部测试都有一个共同的主题:它们都很是简单。 这是学习如何使用基本的测试断言和辅助函数,而且尽量的使用它们的好处之一。编写测试越简单,测试就越容易理解和维护。

掌握了本教程中介绍的 PHPUnit 断言以后,你还能够去 PHPUnit 文档 找到更多内容。 全部的断言都遵循基本的模式,但你会发现,在大多数测试中都会返回基本的断言。

对于 PHPUnit 断言来讲,Laravel 的测试辅助函数是极好的补充,这让应用程序的测试变的很是容易。也就是说,重要的是要认识到,对于咱们写测试,咱们只检查关键信息,而不是整个页面。这使得测试变得简单,并容许页面内容随着应用程序的变化而变化。若是关键信息仍然存在,测试仍然经过,每一个人都会满意。

文章转自: https://learnku.com/laravel/t/24559 更多文章:https://learnku.com/laravel/c/translations

相关文章
相关标签/搜索