2.测试话题

未匹配的标注

本节说明

  • 对应视频第 2 小节:Testing Drving Threads

本节内容

第一个功能测试

以下是 Laravel 5.5 中文文档 中对 Laravel 测试 的简介,详细内容参见文档:

在运行测试时,Laravel 会根据 phpunit.xml 文件中设定好的环境变量自动将环境变量设置为 testing,并将 Session 及缓存以 array 的形式存储,也就是说在测试时不会持久化任何 Session 或缓存数据。

你可以随意创建其它必要的测试环境配置。testing 环境的变量可以在 phpunit.xml 文件中被修改,但是在运行测试之前,请确保使用 config:clear Artisan 命令来清除配置信息的缓存。

我们将Thread定义为话题,现在我们来建立第一个简单的功能测试:a_user_can_browse_theads
首先重命名tests\Feature\ExampleTest.phpThreadsTest.php,修改phpunit.xml文件,配置测试环境:

.
.
<php>
    <env name="APP_ENV" value="testing"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
    <env name="CACHE_DRIVER" value="array"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="QUEUE_DRIVER" value="sync"/>
    <env name="MAIL_DRIVER" value="array"/>
</php>
.
.

我们在内存中进行测试,这样测试运行的速度会快一些,所以在 database 配置项目中我们将使用 sqlite 和 :memory: (Sqlite的内存数据库)。 随着项目迭代测试用例会越来越多所以在将来你可能会需要增加 memory_limit 的值。
编写测试方法:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ThreadsTest extends TestCase
{
    use DatabaseMigrations;

    /** @test */
    public function a_user_can_browse_threads()
    {
        $response = $this->get('/threads');

        $response->assertStatus(200);
    }
}

现在我们已经开始了 TDD 的第一次实践,在使用 TDD 进行开发时,请牢记以下三项法则:

  1. 在编写失败的测试之前,不要编写任何业务代码;
  2. 只要有一个单元测试失败了,就不要再写测试代码。无法通过编译也是一种失败情况;
  3. 业务代码恰好能够让当前失败的测试成功通过即可,不要多写;

接下来我们找到Illuminate\Foundation\Testing\DatabaseMigrations.php文件:

<?php

namespace Illuminate\Foundation\Testing;

use Illuminate\Contracts\Console\Kernel;

trait DatabaseMigrations
{
    /**
     * Define hooks to migrate the database before and after each test.
     *
     * @return void
     */
    public function runDatabaseMigrations()
    {
        $this->artisan('migrate');

        $this->app[Kernel::class]->setArtisan(null);

        $this->beforeApplicationDestroyed(function () {
            $this->artisan('migrate:rollback');
        });
    }
}

可以看到,每次在进行测试的时候,都会先执行php artisan migrate命令初始化数据库;每次执行完测试,都会执行php artisan migrate:rollback命令重置数据库。
现在执行命令运行测试:

$ phpunit

会发现有报错,这是必然的,因为目前我们还未设置路由。前往web.php文件添加路由配置:

Route::get('/threads','ThreadsController@index');

前往ThreadsController.php添加index方法:

public function index()
{
    $threads = Thread::latest()->get();

    return view('threads.index',compact('threads'));
}

注:latest()oldest() 方法允许你轻松地按日期对查询结果排序。默认情况下是对 created_at 字段进行排序。或者,你可以传递你想要排序的字段名称:

$user = DB::table('users')
->latest()
->first();

新建视图文件resources/views/threads/index.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">forum Threads</div>

                    <div class="panel-body">
                        @foreach($threads as $thread)
                            <article>
                                <a href="/threads/{{ $thread->id }}">
                                    <h4>{{ $thread->title }}</h4>
                                </a>
                                <div class="body">{{ $thread->body }}</div>
                            </article>

                            <hr>
                        @endforeach
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

顺手生成一下 Laravel 自带的注册登录功能:

$ php artisan make:auth

再次执行命令即可成功运行:
file
这意味着访问 forum.test/threads ,可以看到:
file

但是,此时的功能测试仅仅代表可以访问该路由,并未达到功能测试的要求。接下来编写真正的测试逻辑:

public function a_user_can_browse_threads()
{
    $thread = factory('App\Thread')->create();

    $response = $this->get('/threads');

    $response->assertSee($thread->title);
}

运行测试:

$ phpunit

测试通过:
file

此时如果我们去掉index.blade.php视图中title属性,则应该测试失败:

.
.
<div class="panel-body">
    @foreach($threads as $thread)
        <article>
            // 
        </article>

        <hr>
    @endforeach
.
.

运行测试,发现测试失败:
file
这证明我们的测试有效。我们继续编写测试,测试单个thread

public function a_user_can_browse_threads()
{
    $thread = factory('App\Thread')->create();

    $response = $this->get('/threads');
    $response->assertSee($thread->title);

    $response = $this->get('/threads/' . $thread->id);
    $response->assertSee($thread->title);
}

运行测试依然会失败,因为还未添加路由、控制器方法跟视图。修改web.php

.
.
Route::get('/threads','ThreadsController@index');
Route::get('/threads/{thread}','ThreadsController@show');

修改ThreadsController.php

.
.
public function show(Thread $thread)
{
    return view('threads.show',compact('thread'));
}
.
.

新建resources/views/threads/show.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        {{ $thread->title }}
                    </div>

                    <div class="panel-body">
                        {{ $thread->body }}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

再次测试,测试成功:
file
此时访问 forum.test/threads/1
file

此时我们的测试是放在一个方法中的,不但可读性不高,并且在功能测试不通过时难以准确定位。让我们将单个测试拆分成a_user_can_view_all_threadsa_user_can_read_single_thread两个测试:

use DatabaseMigrations;

/** @test */
public function a_user_can_view_all_threads()
{
    $thread = factory('App\Thread')->create();

    $response = $this->get('/threads');
    $response->assertSee($thread->title);
}

/** @test */
public function a_user_can_read_a_single_thread()
{
    $thread = factory('App\Thread')->create();

    $response = $this->get('/threads/' . $thread->id);
    $response->assertSee($thread->title);
}

运行测试,成功通过测试:
file
此时在我们的视图文件中,我们采用的是使用url的方式给文章标题附上超链接。可是这种方法可读性差且不利于维护,现在进行修改。
首先在app\Thread.php模型中新增path方法,用来获取链接:

.
.
public function path()
{
    return '/threads/'.$this->id;
}
.

接着修改视图文件index.blade.php

.
.
<article>
   <a href="{{ $thread->path() }}">
       <h4>{{ $thread->title }}</h4>
    </a>
    <div class="body">{{ $thread->body }}</div>
</article>
.
.

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:1
讨论数量: 9
发起讨论 查看所有版本


qhrov
No tests found in class
2 个点赞 | 1 个回复 | 分享
jingzhongwa
assertSee ($thread->title) 无法检测异常
0 个点赞 | 2 个回复 | 问答
DKChen
其它配置
0 个点赞 | 1 个回复 | 分享
DKChen
修正
0 个点赞 | 1 个回复 | 分享
walt-white
需要安装 SQLite 吗
0 个点赞 | 0 个回复 | 问答
tiroGuang
phpunit7.0 版本出现的错误
0 个点赞 | 0 个回复 | 问答