[譯] 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 。

這是一個至關長的章節,由於測試是一個很大的話題,而一個章節沒法給出公正的評價。可是,正如我所提到的,測試並非本書的重點。在隨後的章節中,將再也不進行測試。

下一章咱們討論一些如何讓系統更快的話題如何?

相關文章
相關標籤/搜索