Laravel 单元测试实战(1)- 以电商优惠券设计为例编写单元测试

Laravel 单元测试实战(1)- 以电商优惠券设计为例编写单元测试

单元测试的理解

本文理解的单元测试,不含任何外部系统,包括数据库。

需求

为了写这个文章,虚拟一个电商项目,要求,每次用户购买商品时,自动帮用户从他自己的多张优惠券中选择一张优惠力度最高的优惠券。
优惠券有满减固定金额,满减折扣两种。

表结构:

用户表 users

字段 类型 含义
id int 自增主键
user_name string 用户名,登录用

商品表 goods

字段 类型 含义
id int 自增主键
product_name string 商品名
price decimal(8,2) 价格

店铺表 shops

字段 类型 含义
id int 自增主键
shop_name string 店铺名

商品店铺关联表(多对多)shop_goods

字段 类型 含义
id int 自增主键
shop_id int 店铺id
goods_id int 商品id

系统优惠券表 system_coupons

字段 类型 含义
id int 自增主键
coupon_name string 优惠券名称
type tinyint 1满减固定额度,2满减折扣
coupon_value decimal(8,2) 满减比如5表示可以减去5元,折扣比如0.8表示8折

用户优惠券表

字段 类型 含义
id int 自增主键
user_id int 用户id
system_coupon_id int 对应的系统优惠券id
coupon_name string 优惠券名称
type tinyint 1满减固定额度,2满减折扣
coupon_value decimal(8,2) 满减比如5表示可以减去5元,折扣比如0.8表示8折
condition_money decimal(8,2) 满减的条件,一个订单的总金额大于等于此值

订单表

框架

Larave 8.5

github 项目地址

git clone https://gitee.com/three_kingdoms_of_zhang/unit_test_practice.git
composer install
git checkout v1.0

代码是完整的,包括迁移,模型类,和全部功能。

需求分析

用户在商品页面选择商品和数量,加入购物车,可以选择多种不同的商品,最后点击购买,此时前端调用接口,会获得一张优惠券,然后把总价和系统自动选择的优惠券显示在第二个页面(注意测试订单并未生成)。
本文的目的就是写这个接口,和对这个接口的部分单元测试。
因为接口实际调用数据库,这部分只能通过集成测试来实现。

控制器编写,

<?php

namespace App\Http\Controllers\Order;

use App\Http\Controllers\Controller;
use App\Models\Goods;
use App\Models\UserCoupon;
use App\Services\Order\ServiceOrder;
use Illuminate\Support\Collection;

class OrderController extends Controller
{
    public function find_user_coupon(Request $request, ServiceOrder $serviceOrder)
    {
        $user_id = $request->input('user_id');
        // 用户持有的优惠券。
        $user_coupon_records = UserCoupon::query()
            ->where('user_id', $user_id)
            ->where('use_status', 0)
            ->get();

        // 购物车格式
        // $shopping_cart = [
        //            [
        //                'goods_id' => 1,
        //                'count' => 3,
        //            ],
        //            [
        //                'goods_id' => 2,
        //                'count' => 1,
        //            ],
        //            [
        //                'goods_id' => 3,
        //                'count' => 2,
        //            ],
        //        ];
        $shopping_cart = $request->input('shopping_cart');
        //根据购物车查商品。
        $goods_records =collect([]);
        foreach ( $shopping_cart as $goods_count ){
            $goods_records->push( Goods::findOrFail($goods_count['goods_id']) );
        }

        //根据购物车内容,及查出的商品价格,还有用户已有的优惠券。去查找最优的优惠券。
        $user_coupon_record = $serviceOrder->find_user_coupon_from_shopping_cart(
             $shopping_cart, $goods_records ,$user_coupon_records
        );

        $data =[
           'code' =>0,
           'data' =>  $user_coupon_record,
        ];

        return response()->json($data)->setEncodingOptions(JSON_UNESCAPED_UNICODE);
    }



    public function test()
    {

    }


}

service 编写

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2022/9/23
 * Time: 13:14
 */
namespace App\Services\Order;


use Illuminate\Support\Collection;

class ServiceOrder
{

    /**
     * 根据购物车信息,和已有的优惠券,查找最优惠的一张优惠券。
     *
     * @param array $shopping_cart 前端传来的商品信息。
     * // 购物车格式
     * $shopping_cart = [
     *            [
     *                'goods_id' => 1,
     *                'count' => 3,
     *            ],
     *            [
     *                'goods_id' => 2,
     *                'count' => 1,
     *            ],
     *            [
     *                'goods_id' => 3,
     *                'count' => 2,
     *            ],
     *        ];
     *
     * @param Collection $goods_records 自己查的数据库信息,商品的。
     * @param Collection $user_coupon_records 自己查的用户优惠券信息。
     *
     * @return [
     *    'saved_money' => 0, //优惠券自身的等价金额
     *    'user_coupon_record' => [ //优惠券记录,或者null
     *        'id' => 4,
     *        'type' => 2,
     *        'coupon_value' => 0.9,
     *        'condition_money' => 50,
     *    ]
     * ]
     */
    public function find_user_coupon_from_shopping_cart(array $shopping_cart,Collection $goods_records,                                                        Collection $user_coupon_records):array
    {

        return [];
    }
}

单元测试编写

<?php

namespace Tests\Unit;

use App\Services\Order\ServiceOrder;
use PHPUnit\Framework\TestCase;

class ServiceOrderTest extends TestCase
{
    /**
     * 返回一张满减折扣的优惠券。
     *
     * @return void
     */
    public function test_find_user_coupon_from_shopping_cart_return_right()
    {
        $service = new ServiceOrder();

        $shopping_cart = [
            [
                'goods_id' => 1,
                'count' => 3,
            ],
            [
                'goods_id' => 2,
                'count' => 1,
            ],
            [
                'goods_id' => 3,
                'count' => 2,
            ],
        ];

        // 总价 30+ 20 + 60 = 110
        $goods_records = [
            [
                'id' => 1,
                'price' => 10,
            ],
            [
                'id' => 2,
                'price' => 20,
            ],
            [
                'id' => 3,
                'price' => 30,
            ],
        ];

        // 110的一折是 11元,优惠最大,应该返回第4条记录。
        $user_coupon_records = [
            [
                'id' => 1,
                'type' => 1,
                'coupon_value' => 5,
                'condition_money' => 1000,
            ],
            [
                'id' => 2,
                'type' => 1,
                'coupon_value' => 5,
                'condition_money' => 50,
            ],
            [
                'id' => 3,
                'type' => 1,
                'coupon_value' => 6,
                'condition_money' => 50,
            ],
            [
                'id' => 4,
                'type' => 2,
                'coupon_value' => 0.9,
                'condition_money' => 50,
            ],

        ];

        $result = $service->find_user_coupon_from_shopping_cart( $shopping_cart, collect($goods_records),  collect($user_coupon_records ) );

        $result_expect = [
            'saved_money' =>11,
            'user_coupon_record'=>[
                'id' => 4,
                'type' => 2,
                'coupon_value' => 0.9,
                'condition_money' => 50,
            ],
        ];
        $this->assertEquals($result['saved_money'], $result_expect['saved_money']);
        $this->assertEquals($result['user_coupon_record']['id'], $result_expect['user_coupon_record']['id']);

    }
}

执行单元测试

./vendor/bin/phpunit ./tests/Unit/ServiceOrderTest.php

应显示测试失败。

总结:

先把代码的控制器写出来,service 类必须定义好入参,返回格式,但是函数体为空,然后写好单元测试代码。
按惯例,先执行一下测试,查看到错误。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

单元测试这个习惯好

4天前 评论

api接口测试,中间件token验签怎么跳过了

4天前 评论
yyy123456 (楼主) 4天前
yyy123456 (楼主) 4天前
PHPer技术栈 (作者) 4天前
 /**
     * 列表单元测试
     * @Author: Belief
     * @Time: 2022/09/27 上午 10:25
     * @return void
     */
    public function testListExample()
    {
        $token = $this->testUserToken();
        $response = $this->json('GET', "/v2/test/list?authorization={$token}", [
            'type' => 1,
            'page' => 1,
            'limit' => 2
        ]);
//        $response->dump($token);
        $response->assertStatus(200);
    }

    /**
     * 测试token
     * @Author: Belief
     * @Time: 2022/09/27 上午 11:24
     * @return mixed
     */
    public function testUserToken()
    {
        $response = $this->json('POST', '/auth/login', [
            'username' => 'test',
            'password' => '123456'
        ]);

        $response->assertStatus(200);

        return json_decode($response->getContent())->token;
    }
4天前 评论
yyy123456 (楼主) 4天前

上述单元测试最终运行结果
Laravel

4天前 评论

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