教程:Laravel + Vuejs + Tailwind CSS 构建一个 Todo App 第四部分

Laravel

我们已经在最后几篇文章中添加了一些 Tailwind 样式, 也设置了一个 Vue 组件, 还完成了 Laravel 后端对于我们的 todo 应用,现在看起来像这样:

现在,我们已经将所有这些工作都放入到应用中,如果我们能预见哪些是容易出错的地方,或者说将来我们要升级,做一些重大的重构,我们能做到心中有数岂不是很吊?

如果你对上述的问题回答都是肯定的。是时候进入测试了。在开始之前,我们需要先了解一下问题。

测试对于大部分的人是一件恐怖的事情。但是,它不全是这样的,从最根本的角度来说,编写测试,只是为了让我们知道我们的代码正在按照预期的进行。

不必担心单元测试,功能测试和集成测试之间的差异。不必担心尝试获取 100% 的代码覆盖率。不必担心设置疯狂的 CI 流程。所有这些都可以在很久以后实现。

首先,让我们只关注编写一些简单的测试,这些测试可以帮助我们发现代码什么时候不起作用了。

让我们探探究竟。要继续下去本教程,可以在 GitHub上下载该项目的副本。

測試什麼

在我們進入代碼之前,讓我們花點時間想一下哪些是真正需要測試的部分。基於我們使用 Vue.js 及 Laravel,我們的網站屬於混合傳統服務端渲染以及 SPA 的類型。

即使這今天可能已經是一種常見的模式,它仍然會讓我們的測試有點棘手。我們無法「只」撰寫 Laravel 的測試,也無法「只」撰寫 Vue.js 的測試。反過來說,我們兩邊都需要做一點來確保我們的應用被很好的測試。

現在,有幾種不同的方式來達成。我們可以使用一些工具如 Laravel Dusk 來在我們的應用做瀏覽器測試。但瀏覽器測試運行速度很慢,而且在我的經驗裡,會有點難維護。

相反的,讓我們把 Laravel 端像 API,Vue 端像 SPA 一般對待。我們可以使用 Laravel 的 PHPUnit 測試庫來做 API 的功能測試,以及 Vue Test Utils 來測試我們 SPA 的功能。

设置

测试最难的部分就是在开始阶段把所有部分都设置好。幸运的是,Laravel可以帮助我们更简单的进行测试设置以及运行测试。

Laravel测试设置

还记得设置我们的数据库的时候,我们用的是MySQL。 这对于测试来说非常好,因为我们不用再专门设置一个测试用的数据库. 并且我们可以使用内存中的SQLite数据库。

对于设置我们的测试数据库, 我们需要打开我们项目根目录下的 phpunit.xml 文件。 该文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

这是我们测试的设置文件。简而言之, 它能把测试需要的参数覆盖到我们之前在 .env 文件中设置的参数之中。

在文件的底部,我们可以看到由 <php></php> 标签取代的代码块。在这里,我们可以更新或者增加测试替代项。因此,我们可以在这里设置 SQLite 数据库。我们可以在这里添加以下内容:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

这里,我们的应用使用 sqlite 数据库,而不是使用 mysql,我们的数据库只是存在内存中,也不是命名的数据库。

现在,可以将注意力放到 Vue.js 测试设置了。

Vue.js 的测试设置

在我们的项目中,可以利用 Laravel Mix 来运行我们的 Vue.js 测试。现在,我们只需要用 Yarn 插入我们的测试包即可。

幸运的是,有人已经完成了这个过程 on Laracasts, 所以你可以在他的基础上进行设置。根据他的指示,我们需要安装一些必要的安装包。

yarn add --dev vue-test-utils mocha mocha-webpack jsdom jsdom-global expect moxios

安装了必要的包后,我们回到项目根目录下的 tests 目录,在这个目录下面,我们可以创建一个 Vue 的目录。并在其中添加一个 setup.js 文件,在这个文件中,我们可以添加以下内容:

require('jsdom-global')();

global.expect = require('expect')
global.axios = require('axios');
global.Vue = require('vue');
global.bus = new Vue();

这将为我们的 Vue 测试导入 jsdom-global  包,以及设置一些我们需要的全局变量。这些变量包括 expect库, AJAX 依赖项 axios 和我们的全局事件总线。

回到项目的根目录,我们可以打开package.json文件来添加一个新的测试脚本供我们运行。 在 scripts 对象的底部,我们可以添加以下命令:

"scripts": {
    "dev": "npm run development",
    "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "npm run watch -- --watch-poll",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "prod": "npm run production",
    "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    //New Command Below
    "test-js": "mocha-webpack --webpack-config=node_modules/laravel-mix/setup/webpack.config.js --require tests/Vue/setup.js tests/Vue/**/*.spec.js"
},

现在,当执行 npm run test-js命令的时候,我们可以运行我们的 Vue.js 测试套件!

看起来似乎有些不知所措,但这就是我们开始编写测试所需的全部设置。 现在,让我们开始测试我们的Laravel API。

測試我們的 Laravel 接口

在我們應用的 Laravel 這端,我們已經定義了我們的接口。這是我們的前端如何與資料庫互動,以保存及修改資料的管道。

為了測試這部分,我們想知道幾件事。我們的接口是否回傳預期的響應?如果我們在修改或儲存資料,他是否被永久保存在資料庫裡了?還有,如果我們傳送錯誤的資料,他是否會回傳預期的錯誤訊息?讓我們挖的更深一點。

在我們項目的根目錄,我們可以看到一個 tests 文件夾。這是我們撰寫所有測試的地方。在這裡,我們可以看到一些配置檔及一些文件夾。目前,我們只關心裡面的 Feature 文件夾。

现在让我们用php artisan命令在Feature文件夹下面生成我们的测试类. 这里是我们实际要写测试代码的地方. 为此, 我们可以直接运行命令 php artisan make:test TodoTest

很明显,现在就可以看到我们需要完成的测试类了. 开箱即用的感觉, 生成的测试类向我们展示了一个简单的测试例子.应该看起来就像下面这样:

 /**
 * 一个基础的测试例子
 *
 * @return void
 */
public function testExample()
{
    $this->assertTrue(true);
}

没有什么花里胡哨的,我们只是断言 true 等于 true. 断言这个词你可以在我们的测试中随处可见. 断言是基于我们希望从代码里面实现什么而来的. 既然如此, 我们断言true等于true就只是因为我们希望它是这样.虽然听起来就好像我的经济学教授跟我说的那样,"Nick 你那是赘述".随着我们写更多的一些测试,我们就可以更容易地理解所说的这些东西了.

第一个测试控制器中  index() 方法。我们可以将默认测试替换成以下内容:

use RefreshDatabase;

/** @test */
public function can_get_all_the_todos()
{
    //
}

在顶部,我们使用了 RefreshDatabase 类,因此,我们的 database 是在测试之前实例化,并在完成后删除。接着我们增加了一个可读性极佳的方法来描述我们要测试的内容。现在,可以编写实际的测试内容了。

对于这个测试,我们期望能够获取所有的 todos,因此,我们需要先有一些 todo。

这时候我们的 factory 派上用场了:

/** @test */
public function can_get_all_the_todos()
{
    $todos = factory('App\Todo', 3)->create();
}

如你所见,我们见了 3 条测试的数据,接下来访问 todos 接口:

/** @test */
public function can_get_all_the_todos()
{
    $todos = factory('App\Todo', 3)->create();

    $this->get('/todos');
}

目前为止都很顺利! 我们已经创建了 todo 数据,并且调用我们的API,接下来我们看下控制器:

//In TodoController.php
public function index()
{
    return response(Todo::latest()->get(), 200);
}

查看我们的代码,我们期望我们的API会返回200状态码,并且其中包含所有待办事项。 因此,对于我们的测试,我们应该断言状态码是否等于200,并且响应的数据是否与工厂数据匹配。

/** @test */
public function can_get_all_the_todos()
{
    $todos = factory('App\Todo', 3)->create();

    $this->get('/todos')
        ->assertStatus(200)
        ->assertJson($todos->toArray());
}

现在,验证我们的代码是否符合期望!让我们运行测试查看是否通过。 为此,可以打开命令行并执行 phpunit --filter=TodoTest

我们的测试通过了! 你可以试着让代码报错,并看是否测试还可以通过。 例如,如果将控制器方法中的查询替换为数组,则会看到测试失败,因为数据不包含待办事项的数据:

我们已经完成了待办事项列表的测试,接下来我们创建一个新的测试,来测试下创建 todo 的功能:

/** @test */
public function can_create_a_todo()
{
    //
}

首先我们需要创建一些测试数据:

/** @test */
public function can_create_a_todo()
{
    $todo = factory('App\Todo')->make()->toArray();
}

请注意这一次我们不将创建的假数据入库,所以不用 factory() 的  ->create() 方法,而是 ->make()

我们会发送接口请求创建 todo,并且检查数据库是否有数据:

/** @test */
public function can_create_a_todo()
{
    $todo = factory('App\Todo')->make()->toArray();

    $this->post('/todos', $todo)
        ->assertStatus(201)
        ->assertJson($todo);

    $this->assertDatabaseHas('todos', $todo);
}

一切就绪,接下来运行测试:

快看, 发生了什么?我们拿到的返回码是 302 而不是我们期待的  200返回码 . 302 通常与验证相对应, 所以我们的数据可能不正确. 那我们来看看控制器方法和 factory 是否有效.

//TodoController 中的 控制器方法
public function store(Request $request)
{
    $data = $request->validate([
        'text' => 'required',
        'finished' => 'required|boolean',
    ]);

    $todo = Todo::create($data);

    return response($todo, 201);
}

//TodoController 中的 factory
$factory->define(App\Todo::class, function (Faker $faker) {
    return [
        'text' => $faker->sentence,
    ];
});

问题在这里! 控制器方法需要我们设置 finished属性,但是我们 factory 中并没有,我们应该像这样修改我们的 factory

$factory->define(App\Todo::class, function (Faker $faker) {
    return [
        'text' => $faker->sentence,
        'finished' => false,
    ];
});

运行测试应该可以看到绿色:

果然,我们做到了。至此我们应该可以感受到测试的魅力,它可以保证我们的项目永远按照我们预期的方式工作。

接下来我们测试下创建成功后返回 302 状态码:

/** @test */
public function can_get_a_validation_error_when_trying_to_create_an_empty_todo()
{
    $this->post('/todos', [])
        ->assertStatus(302);
}

有了这些测试,我们可以确保创建 todo 时程序是正常工作的。

以上的测试比较简单,你应该可以跟着做,如果你不幸遇到问题,可以参考源代码仓库  源代码仓库 。

接下来我们一起看下 Vue.js 相关的测试。

测试我们的 Vue.js SPA 前端

先创建 TodoList.spec.js 文件并存储于  tests/Vue 目录中。

此文件的顶部引入之前下载过的库:

import { shallow, mount } from 'vue-test-utils';
import expect from 'expect';
import TodoList from '../../resources/assets/js/components/todo-list.vue';
import moxios from 'moxios'

vue-test-utils 和 expect 是必备的测试工具。TodoList 是我们编写的模块, moxios 是用来 mock 我们的 API 调用的。

接下来开始编写测试代码。所有的测试都写在 describe 里,接下来我们添加一个在 import 代码的下面:

describe('TodoList', () => {
    //Tests go here
});

Now, in order to use moxios, we need to install and uninstall it between each test. Fortunately, we don't have to write that for every test. We can just hook in the beforeEach() and afterEach() calls.

现在,因为使用  moxios 的缘故,我们需要在每个测试之间安装和卸载 axios。写在 beforeEach()afterEach() 钩子里接口:

describe('TodoList', () => {
    beforeEach(() => {
        moxios.install(axios);
    });

    afterEach(() => {
        moxios.uninstall(axios);
    });
});  

接下来我们开始撰写测试。

让我们从简单的事情开始:当组件渲染时,它应该显示  Todo List。因此,在 afterEach 钩子下面,我们可以这样写:

it('renders the correct title on the page', () => {

});

这是我们的测试。类似于我们之前看到的 PHPUnit 简单方法。现在,让我们添加组件:

it('renders the correct title on the page', () => {
    let wrapper = shallow(TodoList);

});

我们使用了 shallow() 方法来创建组件, shallow() 和 mount() 不同,因为它会生成该组件所有子组件的「stubbed out」或者虚拟版本。在这个测试中,我们不太在乎 TodoItem,所以我们可以使用 shallow()。现在,让我们断言。

it('renders the correct title on the page', () => {
    let wrapper = shallow(TodoList);

    expect(wrapper.html()).toContain('Todo List');
});

这里,我们只是希望我们的组件 HTML 包含文本「Todo List」。这有多简单?让我们看看使用之前定义的命令运行测试是否会变成绿色: npm run test-js.

我们已经通过了测试。接着再写一个,用来测试当拥有 todo 时,我们的组件能否正常展示。我们再次定义测试:

it('shows the todos fetched from the api', (done) => {

})

注意我们已经将 done 传递给了方法。这是因为我们将进行异步的  axios 调用。因此,测试结束时,我们需要手动告诉测试。否则即使是测试失败,我们也会始终保持绿色。 

现在,继续挂载组件:

it('shows the todos fetched from the api', (done) => {
    let wrapper = mount(TodoList);
})

这一次,请注意我们正在使用 mount() 而不是 shallow() ,因为我们希望页面上展示子组件。现在,我们可以使用 moxios 调出 axios 调用所期望的响应。

it('shows the todos fetched from the api', (done) => {
    let wrapper = mount(TodoList);

    moxios.stubRequest('/todos', {
      status: 200,
      response: [
          {
            id: 1,
            text: "Bingo",
            finished: false,
            created_at: "2018-01-10 00:00:00",
            updated_at: "2018-01-10 00:00:00",
          }
      ],
    });
})

还记得之前,当我们创建 TodoList 组件时,它就会消失并且从我们的 API 中获取所有的 todos。在这里,我们的组件从  moxios 获取数据,而不是访问 API。

这种方法有它的优点和缺点。我认为它可以使我们快速启动并运行,现在我们需要断言。

it('shows the todos fetched from the api', (done) => {
    let wrapper = mount(TodoList);

    moxios.stubRequest('/todos', {
      status: 200,
      response: [
          {
            id: 1,
            text: "Bingo",
            finished: false,
            created_at: "2018-01-10 00:00:00",
            updated_at: "2018-01-10 00:00:00",
          }
      ],
    });

    moxios.wait(() => {
      expect(wrapper.html()).toContain('Bingo');
      done();
    });
})

这一次,我们做的事情和以往有所不同。首先,我们将断言包装在 moxios.wait() 方法。因为 moxios 实际上是在存根中模拟响应。因此,我们需要在响应完成才能断言。如果我们尝试在没有它的情况下运行测试,那么每次都会失败。因为我们在得到正确数据之前已经断言了。

在这里面,实际的断言看起来很熟悉。因为这和我们之前做的一样。最后,你会注意到 done() 方法。我们将让测试套件知道测试已经完成了。

让我们运行测试看看会得到什么。

你可以看到,我们的测试通过了。我们开始了解整个测试的过程。现在,我们已经完成了一些示例,我将剩余的示例交给你。不用担心,如果你遇到困难,可以随时查看 回顾.

总结

先这样把,今天我们做了很多的介绍。我们逐步介绍了如何进行 Laravel 和 Vue.js 测试的基本配置,并且测试了 API 和 Vue.js 组件!至少在这个功能上,我认为您已经可以自己动手试试了。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://nick-basile.com/blog/post/testin...

译文地址:https://learnku.com/laravel/t/37136

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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