使用 Laravel 進行商品功能測試

lxzoliver發表於2020-07-14

在檢視Laravel的HTTP測試,在開發過程中,進行相關的功能測試是有必要的。

以下是我進行的相關api功能測試:

通過命令php artisan make:test GoodsTest建立一個功能測試類

建立的預設頁面如下:

class GoodsTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

在呼叫相關介面時,首先需要進行使用者認證,首先我需要獲取使用者登入token,並在請求api介面時,在請求頭部新增token,以此完成使用者認證。

定義登入路由

Route::group(['namespace' => 'Auth'], function () {
        //提交登入資訊
        Route::any('/login', 'LoginController@login')->name('admin.login');
});

登入介面程式碼如下:

public function login(Request $request)
    {
        $info = $request->input('info');
        $token = Auth::guard('admin')->attempt(['email'=>$info['email'],'password'=>$info['password']],true);
        $admin = Auth::guard('admin')->user();
        if(!$admin){
            return $this->failed('賬號密碼錯誤,請重新輸入');
        }
        return $this->success($token);
    }

以上程式碼為判斷使用者賬號密碼是否正確,若正確返回token,響應狀態為200;否則響應狀態為400;

編寫新增模擬登入測試方法

    /**
     * @test
     * @return void
     * 模擬使用者登入
     */
    public function login()
    {
        $data = [
            'email' => 'test@qq.com',
            'password' => '123456'
        ];
        $response = $this->json('GET',route('admin.login'),['info'=>$data]);
        $response->assertStatus(200);
        $token = $response->json();
        return $token;
    }

這裡以電商中的商品為例,對商品的建立,刪除進行相關的功能測試。

商品建立測試

在以下例子中,首先通過獲取Faker例項,Faker可以方便我們建立測試資料,詳細使用可檢視faker資料填充詳解

建立商品的測試方法依賴於login方法,通過接受login方法傳遞的token,在請求建立商品介面中,傳遞token完成使用者認證,並傳遞商品資料和商品屬性資料。後驗證介面狀態是否正確,資料庫是否存在相關商品資料和商品屬性資料。

    /**
     * 新增商品資料
     * @test
     * @depends login
     * @param $token
     */
    public function create_goods($token)
    {
        // 獲取 Faker 例項
        $faker = \Faker\Factory::create();
        //商品資料(商品名稱,標題,描述,價格,商品詳情,商品圖片,屬性總攬陣列)
        $info = [
            'name' => $faker->sentence(5,true),
            'title' => $faker->sentence(5,true),
            's_desc' => $faker->text(),
            'position' => $faker->numberBetween(1,100),
            'good_grade' => $faker->numberBetween(1,5),
            'weight' => $faker->numberBetween(100,1000),
            'old_price' => $faker->randomFloat(2, 10, 15),
            'sale_price' => $faker->randomFloat(2, 5, 10),
            'content' => $faker->randomHtml(),
            'mask' => $faker->imageUrl(),
            'attr_list' => [
                "a" =>  [
                  "index" => "a"
                  "name" => "color"
                  "list" => array:2 [
                    "k0" => "red"
                    "k1" => "yellow"
                  ]
                ]
                "b" =>  [
                  "index" => "b"
                  "name" => "size"
                  "list" => array:3 [
                    "k0" => "big"
                    "k1" => "small"
                    "k2" => "middle"
                  ]
                ]
        ];
        //屬性預期數量
        $attrCount = collect($info['attr_list'])->map(function ($attr){
            return count($attr['list']);
        })->reduce(function ($count,$item){
            return $count*$item ? $count*$item : $item;
        });
        //商品屬性資料(包括商品屬性名,屬性標識,舊價格,現價,商品sku,庫存等)
        $goodsAttr = [
            [
                'name'=>'red/big',
                'attr'=>"[\"ak0\",\"bk0\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
            [
                'name'=>'red/small',
                'attr'=>"[\"ak0\",\"bk1\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
            [
                'name'=>'red/middle',
                'attr'=>"[\"ak0\",\"bk2\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
            [
                'name'=>'yellow/big',
                'attr'=>"[\"ak1\",\"bk0\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
            [
                'name'=>'yellow/small',
                'attr'=>"[\"ak1\",\"bk1\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
            [
                'name'=>'yellow/middle',
                'attr'=>"[\"ak1\",\"bk2\"]",
                'sku'=>$faker->uuid,
                'old_price'=>$faker->randomFloat(2, 10, 15),
                'price'=>$faker->randomFloat(2, 10, 15),
                'count'=>$faker->numberBetween(1, 100),
                'weight'=>$faker->numberBetween(1, 100)
            ],
        ];
        $response = $this->withHeader('Authorization',$token)
            ->json('POST','/api/goods',['info'=>$info,'goodsAttrTable'=>$goodsAttr,'attr_list'=>$info['attr_list']]);

        $response->assertStatus(200);
        $data = $response->json();
        //若響應為200返回商品id
        $goodsId = $data;
        //判斷商品是否存在資料庫
        $this->assertDatabaseHas('goods',['id'=>$goodsId]);

        //判斷資料庫屬性數量
        $attr = GoodsAttr::where(['goods_id'=>$goodsId])->get()->toArray();
        $this->assertEquals(count($attr),$attrCount);
    }

在編寫測試用例的過程中,一開始的傳入資料和介面邏輯方面會存在相應問題,我可以通過編寫這種測試用例的方式,不斷完善測試用例和介面程式碼邏輯,以此完善程式碼的容錯性和健壯性。這種開發方式也叫TDD(測試驅動開發).

強烈推薦閱讀以下兩篇文章:
重新認識 PHPUnit —— 從這裡開始學習 PHP 下的 TDD(測試驅動開發)開發方法
使用 TDD 測試驅動開發來構建 Laravel REST API

最終在一步步的修改測試用例和介面程式碼後,通過不斷重複執行phpunit --filter=GoodsTest,最終達到我們測試的預期

使用 Laravel 進行功能測試

刪除商品測試

同樣delete_goods測試方法依賴於login方法,我還對上個例子的create_goods方法做了調整,將建立的商品id返回,做為delete_goods的依賴,並對刪除介面返回狀態碼進行斷言,資料庫軟刪除斷言,獲取商品詳情介面進行狀態碼斷言測試。

    /**
     * 刪除商品
     * @test
     * @depends login
     * @depends create_goods
     * @param $token
     * @param $id
     */
    public function delete_goods($token,$id)
    {
        //商品id
        $goodsId = $id;
        $response = $this->withHeader('Authorization',$token)
            ->json('DELETE','/api/goods/'.$goodsId);
        //驗證狀態碼
        $response->assertStatus(200);
        //是否已被軟刪除
        $this->assertSoftDeleted('goods',['id'=>$goodsId]);
        //獲取商品詳情資訊介面
        $responseEdit = $this->withHeader('Authorization',$token)
            ->json('GET','/api/goods/'.$goodsId.'/edit');
        $responseEdit->assertStatus(404);
    }

使用模型工廠進行批量資料測試

通過命令php artisan make:factory GoodsFactory --model=Goods生成商品的資料工廠

商品的基本資料可通過faker例項建立隨機測試資料,但由於商品的屬性總攬資料相對複雜,我通過自定義函式createAttr()建立生成相應資料。

//定義商品基本資料
$factory->define(Goods::class, function (Faker $faker) {
    return [
        'name' => $faker->sentence(5,true),
        'title' => $faker->sentence(5,true),
        's_desc' => $faker->text(),
        'old_price' => $faker->randomFloat(2, 10, 15),
        'sale_price' => $faker->randomFloat(2, 5, 10),
        'content' => $faker->randomHtml(),
        'mask' => $faker->imageUrl(),
        'attr_list' => createAttr()
    ];
});

function createAttr()
{
    //生成屬性總攬
}

定義資料供給器

定義goods_data方法為資料供給器,在create_goods方法中使用資料供給器進行批量測試

    /**
     * 生成商品資料
     */
    public function goods_data()
    {
        $goodsData = factory(Goods::class,10)->make()->toArray();
        return $goodsData;
    }
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章