Laravel 项目:使用 TDD 构建论坛 Chapter 2

0.写在前面

  • 本系列文章为laracasts.com 的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版
  • 视频源码地址:https://github.com/laracasts/Lets-Build-a-Forum-in-Laravel
  • 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 Laravel 教程 - Web 开发实战进阶 ( Laravel 5.5 ) 类似,可互相参照
  • 项目开发模式为TDD开发,教程简介为:

    A forum is a deceptively complex thing. Sure, it's made up of threads and replies, but what else might exist as part of a forum? What about profiles, or thread subscriptions, or filtering, or real-time notifications? As it turns out, a forum is the perfect project to stretch your programming muscles. In this series, we'll work together to build one with tests from A to Z.

  • 项目版本为laravel 5.4,教程后面会进行升级到laravel 5.5的教学
  • 视频教程共计 102 个小节,笔记章节与视频教程一一对应

1.本节说明

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

2.本节内容

第一个功能测试

测试环境:

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

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

以上是 Laravel 5.5 中文文档 中对 Laravel 测试 的简介,详细内容参见文档 。
现在来建立第一个简单的功能测试: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>
.
.

编写测试方法:

<?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);
    }
}

找到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'));
}

注:latestoldest 方法允许你轻松地按日期对查询结果排序。默认情况下是对 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](https://learnku.com/users/5651) ($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
即意味着访问 http://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](https://learnku.com/users/5651)($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
此时访问 http://forum.test/threads/1
file

此时我们的测试是放在一个方法中的,不但可读性不高,并且在功能测试不通过时难以准确定位,于是将a_user_can_browse_threads测试拆分成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>
.
.

3.笔记心得

4.写在后面

  • 如有建议或意见,欢迎指出~
  • 如果觉得文章写的不错,请点赞鼓励下哈,你的鼓励将是我的动力!
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!