larablog 系列文章 06 - 测试:使用 PHPUnit 进行单元和功能测试

到目前为止,larablog 系列文章就要接近尾声了,过去我们已经探讨了开发的核心概念和方式。在继续添加功能之前,是时候介绍测试相关的内容。我们将研究如何通过单元测试和功能测试确保多个组件与功能一起正常工作。我们将会使用到 PHP 测试库 PHPUnit。通过本章内容,你将会了解如何编写一些覆盖单元和功能测试的用例。

在 Laravel 中的测试

Laravel 在创建时就已考虑到测试的部分。事实上, Laravel 默认就支持用 PHPUnit 来做测试,并为你的应用程序创建好了 phpunit.xml 文件。框架还提供了一些便利的辅助函数,让你可以更直观的测试应用程序。

测试环境

在运行测试时,Laravel 会自动将环境变量设置为 testing,并将 Session 及缓存以 数组 的形式存入,也就是说在测试时不会保存任何的 Session 或缓存数据。

你可以随意创建其它必要的测试环境配置。testing 的环境变量可以在 phpunit.xml 文件中被修改。

单元测试

在计算机编程中,单元测试(Unit Testing) 又称为模块测试,只针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法、包括基类(超类)、抽象类、或者派生类(子类)中的方法。

功能测试

功能性测试检查应用程序中不同元件结合在一起的情况,如网络地址、controllers 与 views。功能测试有点像是你自己会透过浏览器手动进行的测试,如请求首页、点选博客链接以及检查是否显示了正确的文章。功能测试让你可以将这个程序自动化,Laravel 为 HTTP 请求的生成和发送操作、输出的检查,甚至表单的填写都提供了非常流利的 API。

Functional testing(功能测试),也称为 behavioral testing(行为测试),根据产品特性、操作描述和用户方案,测试一个产品的特性和可操作行为以确定它们满足设计需求。

PHPUnit

就如上面的提到的,Laravel 的测试程序是基于 PHPUnit 设计的,如果打开 composer.json,你可以发我们已经将 phpunit 作为开发依赖组件加入到项目中,你可以执行 ./vendor/bin/phpunit --version 查看 PHPUnit 的版本。

单元测试是几个现代敏捷开发方法的基础,使得 PHPUnit 成为许多大型 PHP 项目的关键工具。这个工具也可以被 Xdebug 扩展用来生成代码覆盖率报告 ,并且可以与 phing 集成来自动测试,最后它还可以和 Selenium 整合来完成大型的自动化集成测试。

断言

设计测试程序的目的检查实际测试结果是否与预期测试结果相同,在 PHPUnit 有许多断言方法协助你处理这个部分。部分你会用到的常见断言方法如下:

// Check 1 === 1 is true
$this->assertTrue(1 === 1);

// Check 1 === 2 is false
$this->assertFalse(1 === 2);

// Check 'Hello' equals 'Hello'
$this->assertEquals('Hello', 'Hello');

// Check array has key 'language'
$this->assertArrayHasKey('language', array('language' => 'php', 'size' => '1024'));

// Check array contains value 'php'
$this->assertContains('php', array('php', 'ruby', 'c++', 'JavaScript'));

完整的 断言方法 可以查阅文档。

Laravel 为 PHPUnit 测试提供了一些额外的断言方法:

参考文档 PHPUnit 断言

单元测试

如同之前的提到的,单元测试的目的是独立测试应用程序的个别单元。当你的测试内容较多时,尽量通过测试目录将不同的测试区分开,尽可能保持与开发目录结构一致。

定义并运行测试

要创建一个测试案例,可使用 make:test Artisan 命令,我们这里创建一个文章相关的测试:

php artisan make:test PostTest

此命令会放置一个新的 PostTest 类至你的 tests 目录。接着就可以像平常使用 PHPUnit 一样来定义测试方法。要运行测试只需要在命令行上运行 phpunit 命令即可:

测试 slugify 方法

下面,我们要对之前用于 URL 的格式化文章标题方法 slugify 进行测试。打开 tests/PostTest.php,编辑内容如下所示:

<?php

use App\Post;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class PostTest extends TestCase
{
    public function testSlugify()
    {
        $this->assertEquals('hello-world', Post::slugify('Hello World'));
    }
}

我们写好了一个测试用例,用来测试 slugify 的输出是否与预期的一致。在命令行中运行:

./vendor/bin/phpunit tests/PostTest.php

运行效果如下:

➜  larablog ./vendor/bin/phpunit tests/PostTest.php
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.

.

Time: 98 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

该命令为,运行指定目录下的测试用例。可以看到我的测试通过,没有问题。
接下来,回到 tests/PostTest.php,找到 testSlugify,继续增加断言:

public function testSlugify()
{
    $this->assertEquals('hello-world', App\Post::slugify('Hello World'));
    $this->assertEquals('a day with laravel', Post::slugify('A Day With laravel'));
}

再次运行 ./vendor/bin/phpunit tests/PostTest.php,可以预见结果如下:

➜  larablog ./vendor/bin/phpunit tests/PostTest.php
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.

F

Time: 243 ms, Memory: 8.00MB

There was 1 failure:

1) PostTest::testSlugify
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'a day with laravel'
+'a-day-with-laravel'

/Applications/XAMPP/xamppfiles/htdocs/larablog/tests/PostTest.php:13

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

结果显示有失败的断言,同时可以看到详细的问题所在。很自然的,这是由于我们故意用了错误的预期。

对于 slugify 我们正确的预期是这样:

$this->assertEquals('a-day-with-laravel', Post::slugify('A Day With laravel'));

再次运行 ./vendor/bin/phpunit tests/PostTest.php,测试都通过,满足预期。

功能测试

现在我们已经设计了一些单元测试,接着来看如何测试多个元件一起运作。功能性测试的第一个部分会介绍模拟浏览器请求来测试产生的回应。

关于页面

我们开始测试 PageController 类中的关于页面,由于关于页面相对简单,所以是个适合入手的地方。执行下面的命令:

php artisan make:test PageControllerTest

编辑生成的文件 tests/PageControllerTest.php,内容如下:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class PageControllerTest extends TestCase
{
    public function testAbout()
    {
        $this->visit('/about')
             ->see('About larablog');
    }
}

visit 方法会创建一个 GET 请求,see 方法则断言在返回的响应中会有指定的文本,这是 Laravel 所提供的最基本的应用程序测试。很显然,我们用很简单的方式就自动完成了对关于页面可访问性的测试。

命令行下执行测试:

./vendor/bin/phpunit tests/PageControllerTest.php

你应该会看到测试成功的消息。试着修改 About larablog 的内容为 Contact Us,然后重新执行测试。这个测试现在应该会出现错误,因为页面上找不到 Contact Us,断言不成立。

好了,再继续后面的内容之前,把文字改回 About larablog

主页

虽然关于页面的测试很简单,它已经大致介绍了网页功能性测试的基本原则:

  • 请求页面
  • 检查回应

这是一个简单的处理过程,事实上还有许多其他步骤可以做,比如点击,填写表单内容等。

详细内容可以参考这里 与你的应用程序进行交互

我们来建立一个方法测试首页,我们知道首页的网址是 /,它应该会显示最新的博客文章。在文件 tests/PageControllerTest.php 中新建一个方法 testIndex(),并加入以下内容:

public function testIndex()
{
    $this->visit('/')
         ->see('Continue reading...')
         ->see('Posted by');
}

你可以看到,跟测试关于页面一样的步骤,执行如下命令,看看是否和预期一样:

./vendor/bin/phpunit tests/PageControllerTest.php

接着做进一步的措施,部分功能性测包含能够按照用户的行为在网站上操作,用户为了在页面间跳转会点击链接,我们就试着模拟这个操作来测试博客文章的标题点选后显示博客文章页面的链接是否正确。下面,更新 tests/PageControllerTest.php 类的 testIndex() 方法。

public function testIndex()
{
    $this->visit('/')
         ->see('Continue reading...')
         ->see('Posted by')
         ->click('Continue reading...')
         ->see('Comments')
         ->see('Add Comment');
}

我们通过点击 Continue reading... 链接访问博客文章页面,通过详情页面上存在 Add Comment 来确认访问的是详情页内容。

执行测试来确认首页与博客显示页面的跳转是否正确。

./vendor/bin/phpunit tests/PageControllerTest.php

现在你应该了解如何在网站页面间通过链接来进行功能性测试,接着我们来看看有关表单的测试。

联系页面

larablog 的用户可以在联系页面 /contact 通过填写表单发送联系信息,我们来测试这个表单提交的过程是否正确。
首先我们需要列出表单成功提交的过程(成功提交在这里意味着表单没有错误消息)

  • 访问联系页面
  • 在表单中输入值
  • 提交表单
  • 检查邮件是否发送成功
  • 检查用户端是否收到发送成功的提醒

到目前为止我们只知道如何进行第 1 步和第 5 步,我们现在要看看如何测试中间的 3 个步骤。

在文件 tests/PageControllerTest.php 中新增一个方法 testContact() :

public function testContact()
{
    $this->visit('/contact')
         ->see('Contact larablog')
         ->type('Taylor', 'name')
         ->type('taylor@email.com', 'email')
         ->type('Testing', 'subject')
         ->type('Testing', 'subject')
         ->type('Rerum autem accusantium necessitatibus id. 
            Consequuntur itaque enim similique veniam possimus itaque. 
            Soluta perspiciatis iure sed. 
            Sunt voluptatem est nobis voluptatibus rerum. 
            Dignissimos alias tempore et perspiciatis sequi libero ab dolorum. 
            Saepe consequatur distinctio cumque sunt. 
            Recusandae ut distinctio qui excepturi voluptatem quibusdam et quo. 
            Eius quasi nihil ipsa voluptas molestias.', 'body')
         ->press('Submit')
         ->see('Mail sent successfully. Thank you!');
}

我们用同样的方法,建立了一个访问 /contact 的请求,然后检查页面是否包含 Contact larablog 的标题。接着我们填写表单内容并提交,之后检查响应的信息中是否包含以正确提交的提示。执行这个测试来确认所有功能都正确运作。

./vendor/bin/phpunit tests/PageControllerTest.php

新增文章评论

我们现在已经可以用在联络页面的知识来测试发布文章评论的流程,一样的,我们需要列出表单提成功提交会经历的流程。

  • 访问博客文章页面
  • 在表单中输入值
  • 提交表单
  • 检查新的评论是否被加入到文章评论的列表最后
  • 同时检查右侧 sidebar 最新评论中是否出现了这条新的评论

执行如下的命令创建测试类:

php artisan make:test CommentTest

在建立的文件 tests/CommentTest.php 中加入以下内容:

public function testAddBlogComment()
{
    $post = App\Post::first();

    $this->visit('/'.$post->id.'/'.$post->slug)
         ->see($post->title)
         ->type('Michael', 'user')
         ->type('This is test comment by Michael', 'comment')
         ->press('Submit')
         ->see('<p><span class="highlight">Michael</span> commented')
         ->see('<p class="small"><span class="highlight">Michael</span> commented on');
}

执行测试确认功能是否是否正确:

./vendor/bin/phpunit tests/CommentTest.php

在我们的表单提交后,我们通过检查响应来获取到文章的评论。我们检查文章评论列表中的内容以及侧边栏中的最新的评论信息,确认测试结果满足预期。

总结

我们已经提到一些关于测试重要的地方,我们也介绍了单元测试和功能测试来确保我们网站的功能正确执行。我们已经看到如何模拟浏览器请求以及如何使用辅助方法来检查请求响应。

虽然我们讲了一些内容,很显然这不是 Laravel 测试的全部,你可以通过这里的 文档 来了解更多的细节。

推荐阅读更多精彩内容

  • 前阵子看了点Laravel源码,越看越乱,网上大部分中文文档都是直译,比较生涩难懂,还是决定看英文文档顺便就我的理...
    Bill_Wang阅读 1,242评论 0 2
  • 测试 简介 测试是 Laravel 构建的核心理念。事实上,Laravel 开箱即用的支持 PHPUnit 测试,...
    Dearmadman阅读 5,949评论 2 27
  • 转:http://www.jianshu.com/p/d5fca0185e83 Xcode测试 前言 总算在今天把...
    测试小蚂蚁阅读 1,121评论 0 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 68,683评论 12 116
  • 三月的季节大地焕然一新,小草探出了头在四处张望,花儿在微风中向人们招手,冰冻的河流潺潺的在流水,人们悄悄的换了...
    绿叶的深情阅读 40评论 0 0