[译] Laravel 5 之美 - 单元测试

原文地址: Laravel 5.1 Beauty - Testingjavascript

Note 本系列第四节内容.php

本章会建立一个之后能够用到的项目便于之后咱们的课程使用, 同时也会查课各类测试选项. 之后一段时间内会开发一个 Markdown 文本转换成 Html 的服务信息.css

建立 l5beauty 项目

根据以前的章节 Six Steps to Starting a New Laravel 5.1 Project 建立 l5beauty 项目下面所示项目java

首先在你的系统安装 app 框架node

Step 1 - 安装项目框架

$ laravel new l5beauty
Crafting application...
Generating optimized class loader
Compiling common classes
Application key [rzUhyDksVxzTXFjzFYiOWToqpunI2m6X] set successfully.
Application ready! Build something amazing.

接下来设置 l5beauty.app 做为虚拟主机.mysql

Step 2 - 配置服务器

$ serve l5beauty.app ~/l5beauty/public
dos2unix: converting file /vagrant/scripts/serve.sh to Unix format ...
 * Restarting nginx nginx                                                [ OK ]
php5-fpm stop/waiting
php5-fpm start/running, process 2169

回到主机, 添加如下记录到映射文件nginx

Step 3 - 添加 l5beauty.app 到映射文件

192.168.10.10  l5beauty.app

从主机中, 按照如下步骤安装 NPM 本地包laravel

Step 4 - NPM 本地安装

$ cd l5beauty
$ npm install
|
> node-sass@2.0.1 install /Users/chuck/Code/l5beauty/node_modules/laravel-\
    elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/install.js

> node-sass@2.0.1 postinstall /Users/chuck/Code/l5beauty/node_modules/\
    laravel-elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/build.js

`darwin-x64-node-0.10` exists; testing
Binary is fine; exiting
gulp@3.8.11 node_modules/gulp
├── v8flags@2.0.2
├── pretty-hrtime@0.2.2

[snip]

回到主机, 建立数据库git

Step 5 - 建立应用数据库

$ mysql --user=homestead --password=secret
mysql> create database l5beauty;
Query OK, 1 row affected (0.00 sec)

mysql> exit;
Bye

编辑 .env 文件, 修改数据库为 l5beauty.github

Step 6 - 更改配置文件中的 DB_NAME

// Change the following line
DB_DATABASE=homestead

// To the correct value
DB_DATABASE=l5beauty

最后, 访问 http://l5beauty.app , 确保一切可用.

Figure 6.1 - Step 5 - 在浏览器中测试

clipboard.png

运行 PHPUnit

Laravel 5.1 已经准备好测试了, 有个最简单的方式来检测一个访问是否返回200响应.

运行 PHPUnit , 简单的在根目录上执行 phpunit就OK

运行 PHPUnit

$ cd l5beauty
~/l5beauty $ phpunit
PHPUnit 4.7.4 by Sebastian Bergmann and contributors.

Time: 544 ms, Memory: 10.25Mb

OK (1 test, 2 assertions)

是否有错误 ?

若是你在运行 phpunit 收到 command not found 或者 permissions denied 错误提示, 有多是由于安装问题. phpunit 命令通常会存放在 vendor/bin 目录而且添加进系统变量, 问题是 Laravel 命令有一个bug是没有给这个命令设置相应的权限

使用以下方法解决这个问题:

Step 1 - 删除 vendor 目录

Step 2 - 在代码根目录使用 composer update 命令从新建立 vendor 目录. (操做系统中运行)

这样, 而后从新执行 phpunit

Laravel 5.1 PHPUnit 配置

在 Laravel 5.1 项目根目录中有个文件 phpunit.xml. 这个文件包含使用 phpunit 运行时候的配置

phpunit.xml的测试会放置在 tests 目录, 这里有两个文件

  1. ExampleTest.php - 包含一个测试方法 testBasicExample(). 这个 ExampleTest 类集成自 TestCase 类.
  2. TestCase.php - Laravel 基础测试单元.

查看 testBasicExample() 方法 ExampleTest.php.

testBasicExample() 方法

public function testBasicExample()
  {
    $this->visit('/')
         ->see('Laravel 5');
  }

这个测试告诉咱们 "访问主页而且可以看到内容 ‘Laravel 5’", 还能比这个更简洁么 ?

TestCase 类提供在框架中的应用方法和属性. TestCase 一样提供了一个附加断言列表方法和 crawler 类型测试

Laravel 5.1 Crawler 方法和属性

Crawler 容许你测试web应用. 这些方法都有个统一的优势就是都可以返回 $this , 容许你建立 ->visit()->see() 相似这样的链式调用.

下边是一些属性和方法:

$this->response

web应用返回的最后的响应

$this->currentUri

当前查看的Uri

visit($uri)

(Fluent) 使用 get 方法访问给定的uri

get($uri, array $headers = [])

(Fluent) 使用 get 方法访问url, 并能够传输给定的header

post($uri, array $data = [], array $headers = [])

(Fluent) post 请求

put($uri, array $data = [], array $headers = [])

(Fluent) put 请求

patch($uri, array $data = [], array $headers = [])

(Fluent) PATCH 请求

delete($uri, array $data = [], array $headers = [])

(Fluent) DELETE 请求

followRedirects()

(Fluent) 跟踪最近返回的重定向

see($text, $negate = false)

(Fluent) 查找页面上显示的内容/显示/不显示

seeJson(array $data = null)

(Fluent) 断定请求包含 json, 若是传输了 $data 参数, json 值必须匹配.

seeStatusCode($status)

(Fluent) 相应是否返回指定的状态码

seePageIs($uri)

(Fluent) 当前页面是不是指定的URI

seeOnPage($uri) and landOn($uri)

(Fluent) Alias seePageIs()

click($name)

(Fluent) 经过name 或者 id 来请求点击

type($text, $element)

(Fluent) 填充自定义的文本

check($element)

(Fluent) 检测页面的checkbox

select($option, $element)
(Fluent) 选择下拉项

attach($absolutePath, $element)

(Fluent) 附件

press($buttonText)

(Fluent) 提交指定文本的text

withoutMiddleware()

(Fluent) 禁用 middleware

dump()

输入最近返回的内容

Laravel 5.1 PHPUnit 应用方法

这里有额外的 Laravel 5.1 方法和属性

$app

$app 实例

$code

artisan 返回的最近的 code 码

refreshApplication()

刷新应用, setup()方法会自动调用这个方法.

call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)

调用指定的url 而且返回响应.

callSecure($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)

调用 https 访问url而且返回响应

action($method, $action, $wildcards = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null)

调用控制器方法

route($method, $name, $routeParameters = [], $parameters = [], $cookies = [], $files = [], $server = [], $content = null)

调用路由而且返回方法.

instance($abstract, $object)

注册对象的实例

expectsEvents($events)

指定可能被触发的事件列表.

withoutEvents()

不调用事件

expectsJobs($jobs)

注册队列

withSession(array $data)

使用session

session(array $data)

开始并设置 session

flushSession()

刷新当前session 的内容

startSession()

开始 session

actingAs($user)

(Fluent) 设置当前登陆的用户

be($user)

设置用户

seeInDatabase($table, array $data, $connection = null)

(Fluent) 检测给定的数据是否存在在数据库中

notSeeInDatabase($table, $array $data, $connection = null)

(Fluent) 检测数据是否不存在数据库中

missingFromDatabase($table, array $data, $connection = null)

(Fluent) Alias notSeeInDatabase().

seed()

数据库数据seed 生成器

artisan($command, $parameters = [])

指定 artisan 命令而且返回代码

上边的方法/属性都可以在 test 中 使用, 默认的 ExampleTest.php 中存在一个方法 testBasicExample(), 这个调用了 $this->call(...) 方法.

Laravel 5.1 PHPUnit 断言

除了标准的 PHPUnit 断言(assertEquals(), assertContains(), assertInstanceOf(), ...)以外, 还存在不少容许测试 web 应用的检测项目

assertPageLoaded($uri, $message = null)

检测最近的页面是否被加载, 若是不存在 url / message 时候会报错

assertResponseOk()

是否页面相应OK

assertReponseStatus($code)

是否响应指定的code

assertViewHas($key, $value = null)

视图中是否存在指定的数据

assertViewHasAll($bindings)

视图中是否存在指定的一系列数据

assertViewMissing($key)

指定视图中是否不存在这个数据

assertRedirectedTo($uri, $with = [])

检测是否重定向到指定的uri

assertRedirectedToRoute($name, $parameters = [], $with = [])

是否客户端重定向到指定的路由

assertRedirectedToAction($name, $parameters = [], $with = [])

是否重定向到 action

assertSessionHas($key, $value = null)

session 中是否存在 key/ value

assertSessionHasAll($bindings)

session 中是否存在指定的 kv

assertSessionHasErrors($bindings = [])

session 是否存在错误

assertHasOldInput()

session 中是否存在之前的数据

使用 Gulp 来进行 TDD 测试

Gulp 是用javascript 写成的编译和自动化工具. 基本用来最小化源代码或者从源代码生成文件. Gulp 可以监控源代码的改变而且自动运行指定的任务

Laravel 5.1 存在 Laravel Elixir 容许运行 gulp 任务. Elixir 加入了更简洁的语法. 你这样想 PHP 中的 Laravel, Gulp 中的 Elixir.

一个最经常使用的 Gulp 是自动化测试. 咱们根据 TDD (Test Driven Development) 来自动化运行咱们的测试任务.

首先, 编辑 gulpfile.js 文件, 并按照如下修改.

配置 Gulp 来运行 PHPUnit 测试

var elixir = require('laravel-elixir');

elixir(function(mix) {
    mix.phpUnit();
});

这里咱们调用 elixir() 方法. 传递一个函数. 这个函数接收一个 mix 对象. 这个函数可以干不少你可能想都想不到的事情. 你可能想经过 less 文件编译 css 文件. 而后合并 css 文件, 而后再文件末尾加缀上版本号. 全部的这些事情均可以经过 mix 对象来运行.

可是如今, 咱们仅仅运行 PHPUnit 测试.

接下来, 直接运行 glup

运行 Gulp

~% cd Code/l5beauty
~/Code/l5beauty% gulp
[15:26:23] Using gulpfile ~/Code/l5beauty/gulpfile.js
[15:26:23] Starting 'default'...
[15:26:23] Starting 'phpunit'...
[15:26:25] Finished 'default' after 2.15 s
[15:26:25]

*** Debug Cmd: ./vendor/bin/phpunit --colors --debug ***

[15:26:28] PHPUnit 4.7.4 by Sebastian Bergmann and contributors.

Configuration read from /Users/chuck/Code/l5beauty/phpunit.xml

Starting test 'ExampleTest::testBasicExample'.

Time: 2.07 seconds, Memory: 10.25Mb

OK (1 test, 2 assertions)
[15:26:28] gulp-notify: [Green!]
[15:26:28] Finished 'phpunit' after 4.96 s

你可能收到一个通知, 一个弹框, 绿色告知你全部测试都已经经过了

Figure 6.2 - Gulp’s PHPUnit Success on Windows 8.1

clipboard.png

想要使用 gulp进行自动化测试, 运行 gulp tdd

运行 Gulp

~% cd Code/l5beauty
~/Code/l5beauty% gulp tdd
[15:29:49] Using gulpfile ~/Code/l5beauty/gulpfile.js
[15:29:49] Starting 'tdd'...
[15:29:49] Finished 'tdd' after 21 ms

这个命令挂载在这里, 监听源文件的变化, 而且在须要的时候进行单元测试.

想要查看如何运行的. 让咱们中段存在的单元测试.

改变 tests/ExampleTest.phpsee() 方法.

中段 ExampleTest.php

->see('Laravel 5x');

当你保存这个文件, gulp 将会通知而且从新运行 PHPUnit , 这个将会执行错误. 而后你会看到一个红色的错误提示

Figure 6.3 - Gulp’s PHPUnit Failure on Mac

clipboard.png

若是你要从新更改回来,保存, 而后 gulp 会从新运行 PHPUnit, 而后你会看到绿色的图标

退出 Gulp’s tdd 模式

按下 Ctrl+C

建立 Markdown 服务

在博客应用中咱们会使用 Markdown 语法来写文章, 若是你不熟悉 markdown, 能够经过链接来检查这个语法, 这是一个快速读/写而且可以保存为 HTML.

举例说明, 咱们会建立一个服务项目来生成HTML.

拉取一个 Markdown 包

这里有许多的PHP包来把markdown 转换为HTML, 若是你去 http://packagist.org 这里搜索 markdown , 会发现 20 多页的包.

咱们会使用 Michel Fortin 建立的包. 由于"好", 接下来咱们运行以下的命令来拉取这个包

添加 Markdown 和 SmartyPants

~/Code/l5beauty% composer require michelf/php-markdown
Using version ^1.5 for michelf/php-markdown
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing michelf/php-markdown (1.5.0)
    Downloading: 100%

Writing lock file
Generating autoload files
Generating optimized class loader

~/Code/l5beauty% composer require "michelf/php-smartypants=1.6.0-beta1"
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing michelf/php-smartypants (1.6.0-beta1)
    Loading from cache

Writing lock file
Generating autoload files
Generating optimized class loader

你有没有注意到指定了包版本号. 这是由于写这篇文章的时候尚未一个稳定的版本号可以自动拉取到

建立 Markdown 测试类

开始 TDD session 的第一件事就是开启 TDD 模式

Starting Gulp in TDD mode

~/Code/l5beauty% gulp tdd
[19:41:38] Using gulpfile ~/Code/l5beauty/gulpfile.js
[19:41:38] Starting 'tdd'...
[19:41:38] Finished 'tdd' after 23 ms

如今 gulp 监控 PHPUnit 的改变. 让咱们建立测试类

tests 目录, 建立一个新文件夹命名为 Services 同时建立一个文件 MarkdownerTest.php

初始化 tests/Services/MarkdownerTest.php

<?php

class MarkdownerTest extends TestCase
{

    # 保存 markdown 对象
    protected $markdown;
    
    # 建立 Markdowner 对象
    public function setup()
    {
        $this->markdown = new \App\Services\Markdowner();
    }
    
    # 测试
    public function testSimpleParagraph()
    {
        $this->assertEquals(
            "<p>test</p>\n",
            $this->markdown->toHTML('test')
        );
    }
}

这里会报错

即便告诉你检测失败, 日志中也会存在相应的错误日志, 这里很明显的是 App\Services\Markdowner 这个类不存在.

建立 Markdowner 服务

这里咱们建立一个服务来封装 php-markdown 和 php-smartypants 来加载导入的服务

app\Services 目录建立一个 Markdowner.php 服务并填写如下的内容 .

app/Services/Markdowner 的内容

<?php
# 命名空间
namespace App\Services;
# 导入的类
use Michelf\MarkdownExtra;
use Michelf\SmartyPants;

class Markdowner
{
    
    # 转换类
    public function toHTML($text)
    {
        $text = $this->preTransformText($text);
        $text = MarkdownExtra::defaultTransform($text);
        $text = SmartyPants::defaultTransform($text);
        $text = $this->postTransformText($text);
        return $text;
    }
    
    protected function preTransformText($text)
    {
        return $text;
    }
    
    protected function postTransformText($text)
    {
        return $text;
    }
}

当你保存了这个文件, Gulp 应当显示一个绿色的提示框. 告诉你执行OK.

若是你没有收到绿色的提示. 检测文件和测试类.

更多测试

你们都赞成的是, 这不是一个好的 TDD 测试例子, 由于太简单了. 实际的会有好多操做步骤和迭代, 以下

  • 建立 MarkdownerTest w/ testSimpleParagraph()
  • Tests Fail
  • 建立 Markdowner 类, 硬编码 toHTML() 来经过测试
  • Tests Succeed
  • 更新 Markdowner 类来使用 MarkdownExtra
  • Tests Succeed
  • 添加一个 testQuotes() 到 MarkdownerTest 类
  • Tests Fail
  • 更新 Markdowner 类来使用 SmartyPants
  • Tests Succeed

目前为止, 全部的咱们 Markdowner 类在测试前都是不完整的.要在该类上进行 单元测试,应该将其结构化以便将 MarkdownExtraSmartyPants 类的实例注入到构造函数中, 经过这种方式咱们的单元测试能够注入模拟对象,而且只验证 MarkdownExtra 的行为,而不是它所调用的类.

但这不是一本关于测试的书。事实上,这是讨论测试的唯一一章。咱们会离开这个结构,但须要再添加几个测试。

更新 MarkdownerTest 来和下面的内容一致

app/Services/Markdowner 的最终内容

<?php

class MarkdownerTest extends TestCase
{

    protected $markdown;
    
    public function setup()
    {
        $this->markdown = new \App\Services\Markdowner();
    }
    
    /**
    * @dataProvider conversionsProvider
    */
    public function testConversions($value, $expected)
    {
        $this->assertEquals($expected, $this->markdown->toHTML($value));
    }
    
    public function conversionsProvider()
    {
        return [
            ["test", "<p>test</p>\n"],
            ["# title", "<h1>title</h1>\n"],
            ["Here's Johnny!", "<p>Here&#8217;s Johnny!</p>\n"],
        ];
    }
}

在这里,咱们更改了测试类用来一次测试多个转换,并在 conversionsProvider() 中添加了三个测试。在进行下一步骤以前,你的测试结果应该是绿色的。

在系统控制台中一旦测试是绿色的,按 Ctrl+C 来中止 Gulp

测试的其余方法

这里并非想使用 Laravel 5.1 提供一个完整的测试方法,由于在PHP中没有单独的方法来进行测试。

所以,在 Laravel 5 5.1 中没有单一的测试方法。

可是,咱们将探索一些替代方案

phpspec

除了 PHPUnit ,Laravel 5.1 还提供了 phpspec 。这是另外一种流行的PHP测试,它更多地关注于 BDD(行为驱动开发)

这里有一些关于phpspec的注释。

  • 程序文件在 vendor/bin, 所以你能够在项目根目录下调用 phpspec.
  • 配置文件在根目录, 名字是 phpspec.yml.
  • 从 Gulp 中运行 phpspec , Elixir 提供了 phpSpec() 函数, 你能够在 mix 对象中运行.
  • 若是你把程序的命名空间从 App 改为其余的命名空间, 确保同步更新 phpspec.yml 中的配置.

单元测试

虽然 PHPUnit 是 PHP 单元测试的标准,但也有其余的包可使用

  • Enhance PHP - 单元测试框架支持 mocks 和 stubs.
  • SimpleTest - 另外一个使用 mock 的单元测试框架.

功能 / 验收测试

这些测试真实的使用了您的应用程序,而不是仅仅验证您的应用程序中的代码单元。当使用流畅的测试方法 Laravel 5.1 时,你可使用 PHPUnit 进行一些功能测试. ExampleTest.php 提供了一个简单的示例. 可是也有其余的测试框架关注于功能/验收测试(functional / acceptance testing).

行为驱动开发 (BDD)

BDD(行为驱动开发)有两种方式: SpecBDD 和 StoryBDD

SpecDD 关注代码的技术方面。Laravel 5.1 包含了 _phpspec_, 这是SpecDD的标准

StoryBDD 强调业务或特性测试. Behat 是最流行的 StoryBDD 框架。一样 Codeception 也能够用于 StoryBDD 。

回顾

咱们在这一章所作的第一件事就是建立一个名为 l5beauty 的项目。而后咱们在这个项目中使用 PHPUnit 进行单元测试。最后,咱们建立了一个 Markdowner 服务类,这有两个目的 一是测试, 二是并在之后将 markdown 转换为 HTML 。

这是一个至关长的章节,由于测试是一个很大的话题,而一个章节没法给出公正的评价。可是,正如我所提到的,测试并非本书的重点。在随后的章节中,将再也不进行测试。

下一章咱们讨论一些如何让系统更快的话题如何?

相关文章
相关标签/搜索