Day20 Laravel - test

在Day19时搭配RESTful API风格的CRUD做出了一个留言板的功能,但也发现到了每次要确认功能是否正常的时候都需要打开PostMan一个一个功能测试,那如果之後功能做到好几十个的时候也要这样手动测试吗?为了解决这个问题就有了Test这个功能

Laravel本身就内建了两个Test,直接先下指令来测试一下吧

php artisan test

以/tests/Unit/exampleTest这个范例来说,assertTrue代表的是系统会去测定测试的东西是否为true,那当然这个测试里面给的值是true所以这个测试就会pass,那可以试着把true改成false来试试看,就会看到测试结果为fail。

接着来介绍一下在/laravel资料夹下的phpunit.xml这个档案,会记录着测试的设定

testsuites这个tag设定了要跑哪些test,已预设来说会有Feature及Unit两个资料夹的test,先试着把Unit所属的tag注解起来再跑一次,就会看到系统不会去处理Unit这个资料夹里的test了。

coverage这个tag则是设定要测试覆盖率的范围在哪边,但这次先不介绍这麽深入,稍微记得有测试覆盖率这个词就好。

接下来先将无用的测试移除掉吧

rm tests/Feature/ExampleTest.php
rm tests/Unit/ExampleTest.php

接着进入今天的正题,来建立第一个测试,先以Day19的Message的Index列表功能来做示范,下指令建立测试,重点是最後档名要以Test当结尾,然後因为是Message系列的功能,将他归纳在Message资料夹下面,资料夹不需要另外建立,系统会一并创建

php artisan make:test Message/indexTest

接着就会在Feature/Message/下看到一个indexTest.php的档案,先将档案修改成这个样子

<?php

namespace Tests\Feature\Message;

use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class indexTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $message = Message::create([
            'member' => 'verycow',
            'message' => 'first test',
        ]);

        $response = $this->withHeaders([

        ])->json('GET', '/api/messages', [

        ]);

        $response->dump()->dumpHeaders();
        
        $response->assertStatus(200);
    }
}

修改的部分有

  1. 上方的faker现在并不会用到所以先移除
  2. 最上方引入Message的Model
  3. RefreshDatabase改为使用DatabaseTransactions,并且在class内use DatabaseTransactions;
  4. 因为要测试列表功能是否正常要先塞点资料给系统才能看出功能是否正常,所以先创建一笔测试资料ˊ
  5. withHeaders的部分暂时还不会用到所以先写着不理没关系
  6. json代表送出的request是以JSON格式将资料传送至API
  7. 第一个参数是呼叫API的method,列表是使用GET method来呼叫,所以选择GET,後续也会用到POST、PUT、DELETE等method
  8. 第二个参数是API的url,昨天在route设定的是messages没问题
  9. 第三个参数是bodyRequest,其实GET method是不会有这个内容的,但为了写code的风格完整所以我还是会保留着这个空array
  10. assertStatus代表此测试回传的 web status预期为200

那就下指令跑看看测试吧,测试结果如下
https://ithelp.ithome.com.tw/upload/images/20210920/201150481uJ4uRTdfs.png
这样就有一个最基本的服务是否能正常运作测试啦,当然还有其他的assert能使用,例如回传资讯是否正确

$response->assertStatus(200)
    ->assertJson([
        [
            'member' => $message->member,
            'message' => $message->message,
        ]
    ]);

这个判定代表着回传的Json内容是否为当初塞入的测试资料,也可以故意判定错误的资料,系统也会反馈错误的资料有哪些,在後续除错上会很方便,更多的案例或是assert可以在官网上找到。

那接着先把後续的create、detail、update、delete完成

create - POST method - 预期回传success字串

<?php

namespace Tests\Feature\Message;

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

class createTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $response = $this->withHeaders([

        ])->json('POST', '/api/messages', [
            'member' => 'verycow',
            'message' => 'update test'
        ]);

        $response->dump()->dumpHeaders();

        $response->assertStatus(200)
            ->assertSee('success');
    }
}

detail - GET method - 预期回传特定Message内容,注意细节url後面要提供id

<?php

namespace Tests\Feature\Message;

use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class detailTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $message = Message::create([
            'member' => 'verycow',
            'message' => 'first test',
        ]);

        $response = $this->withHeaders([

        ])->json('GET', '/api/messages/' . $message->id, [

        ]);

        $response->dump()->dumpHeaders();

        $response->assertStatus(200)
            ->assertJson([
                'member' => $message->member,
                'message' => $message->message,
            ]);
    }
}

update - PUT method - 预期回传success字串并且DB内容已修改

<?php

namespace Tests\Feature\Message;

use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class updateTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $message = Message::create([
            'member' => 'verycow',
            'message' => 'test',
        ]);

        $response = $this->withHeaders([

        ])->json('PUT', '/api/messages/' . $message->id, [
            'message' => 'update test'
        ]);

        // $response->dump()->dumpHeaders();

        $response->assertStatus(200)
            ->assertSee('success');

        $this->assertDatabaseHas('messages', [
            'message' => 'update test'
        ]);
    }
}

delete - DELETE method - 预期回传success字串并且DB内资料已被删除

<?php

namespace Tests\Feature\Message;

use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class deleteTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $message = Message::create([
            'member' => 'verycow',
            'message' => 'test',
        ]);

        $response = $this->withHeaders([

        ])->json('DELETE', '/api/messages/' . $message->id, [
            
        ]);

        // $response->dump()->dumpHeaders();

        $response->assertStatus(200)
            ->assertSee('success');

        $this->assertDeleted($message);
    }
}

以上就是最基本的Test,要特别说明一下前面有用到的RefreshDatabase与DatabaseTransactions,如果没有使用这两个Trait,每一次的测试完毕後测试资料都会留存在DB里面,但因为这些测试资料实际上不应该保存下来,所以需要利用这两个Trait来清除资料,那RefreshDatabase与DatabaseTransactions的差别在於,
RefreshDatabase是将整个table移除後再重新建立table,而DatabaseTransactions只会将此次测试的资料移除掉,所以可以自行去测试发现DatabaseTransactions的自增加id会越来越往上加但是使用RefreshDatabase就不会发生这样的问题,但相对的是使用RefreshDatabase会有比较高的效能成本,到後期测试越来越多时会越来越有感受。

其实测试要做的事情就是

  1. 准备好API运作前必须要有的资料
  2. 呼叫该API
  3. 检测API是否如预期运作

还有点时间来介绍一点进阶的Test,首先在列表的时候只建立了一笔测试资料,那如果我想要有多笔测试资料难道要create model三次吗?此时就要搭配Factory来操作,首先建立一个Message的Factory

php artisan make:factory MessageFactory

就可以在database/factories/之下找到MessageFactory档案,Message的资讯有id、member、message、created_at、updated_at,其中id、created_at、updated_at已经会有预设值,所以此时只要设定member与message即可,此时搭配faker不是电竞选手那个faker,faker的意思是使用大神已经写好的套件来自动产出假资料,就不必费神在想要塞哪些假资料了,faker能提供的假资料有非常多,姓名、地址、国家、文章、Email等等都有提供,可至该大神的Github找到可使用的假资料格式,接着将MessageFactory改造如下

<?php

namespace Database\Factories;

use App\Models\Message;
use Illuminate\Database\Eloquent\Factories\Factory;

class MessageFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Message::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'member' => $this->faker->name(),
            'message' => $this->faker->sentence(),
        ];
    }
}

并将indexTest改造如下

<?php

namespace Tests\Feature\Message;

use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class indexTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    use DatabaseTransactions;

    public function test_200()
    {
        $messages = Message::factory(3)
            ->create();

        $response = $this->withHeaders([

        ])->json('GET', '/api/messages', [

        ]);

        // $response->dump()->dumpHeaders();

        $response->assertStatus(200)
            ->assertJson([
                [
                    'member' => $messages[0]->member,
                    'message' => $messages[0]->message,
                ],
                [
                    'member' => $messages[1]->member,
                    'message' => $messages[1]->message,
                ],
                [
                    'member' => $messages[2]->member,
                    'message' => $messages[2]->message,
                ]
            ]);
    }
}

就能非常快速的制造多笔假资料来测试了呢,其实在於写测试这部分还有非常多的流派,譬如BDD、TDD等等流派,但在深究这些流派到底哪些有用之前我觉得最基本的测试写好及一定的覆盖率才是最重要的。

另外一定也会有一个疑问,为什麽不用PostMan测就好还有花这麽多时间另外写测试?就我个人的理解是因为基本的测试是为了後续的CI/CD所铺路,但目前这个阶段离CI/CD还有点距离,有机会再谈。

入职後前三个月都在做的事情就在今天介绍完毕了,谢谢观看的各位,请记得按赞分享开启小铃铛,你的支持会让按赞数+1。


<<:  Day_08 : 让 Vite 来开启你的Vue 之 Vite 核心 HMR

>>:  [Day 15] backtesting 使用说明

jQuery 日历 datepicker

Step 1.add textbox 2.import js 3.bind textbox &...

Day 09:「啊~不要碰我!我会变色~」- 变化模式 (Variants)

欸欸欸!别误会啊! 可别读完标题就跑掉了。 「可是兔兔,你那个标题不妥吧!」 齁,我才是觉得你想的...

Day08 Flutter 和 Native 通讯的原理 02

概念: Flutter 会将资料通过 engine 层传送到 native 层,native 处理...

文件后缀与Mime类型对照表

总觉得有用,先记录下。 以下是一些文件后缀(扩展名)对应的MIME类型的一个对照表,方便iis中或其...

Day04_学资安的心境呢,有一句话可以参考~虐妻一时爽追妻火丧场~只不过你是那个妻,不是夫~XD"

恩~继续一路歪的标题~哈哈哈哈哈~而且你只能是被虐方,不会有追平地位这事的发生~ ▉ISO27001...