Laravel 單元測試實戰(4)- 測試成功後的方法重構並再次測試透過

yyy123456發表於2022-09-26

Laravel 單元測試實戰(4)- 測試成功後的方法重構並再次測試透過

github 專案地址

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

程式碼是完整的,包括遷移,模型類,和全部功能。

需求

程式碼不太好,已經感覺到需要重構
1、控制器裡有大量的資料庫操作。
2、購物車入參 goods_id, count 完全依照註釋在寫,不太好,程式碼應該有強制性保證入參的格式,會更易於閱讀。
3、入參名稱不變,測試程式碼也不變,改變控制器和 service 類

重構之後的程式碼,

控制器
class OrderController extends Controller
{
    /**
     * 查詢優惠券。
     *
     * @param Request $request
     * @param ServiceOrder $serviceOrder
     * @return \Illuminate\Http\JsonResponse
     */
    public function find_user_coupon(Request $request, ServiceOrder $serviceOrder)
    {
        $input_user_id = $request->input('user_id');
        $input_shopping_carts = $request->input('shopping_cart');
        $user_coupon_record = $serviceOrder->controller_find_user_coupon($input_user_id, $input_shopping_carts);

        $data =[
           'code' =>0,
           'data' =>  $user_coupon_record,
        ];
        return response()->json($data)->setEncodingOptions(JSON_UNESCAPED_UNICODE);
    }

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


use App\Models\Goods;
use App\Models\UserCoupon;
use Illuminate\Support\Collection;

class ServiceOrder
{
    /*
     * 查詢優惠券。
     *
     * @param $input_user_id
     * @param $input_shopping_carts array 購物車格式
     *         [
     *            [
     *                'goods_id' => 1,
     *                'count' => 3,
     *            ],
     *            ... ...
     *        ];
     *
     * @throws \Exception
     */
    public function controller_find_user_coupon($input_user_id,$input_shopping_carts )
    {
        // 使用者持有的優惠券。
        $user_coupon_records = UserCoupon::query()
            ->where('user_id', $input_user_id)
            ->where('use_status', 0)
            ->get();

        $shopping_carts = [];
        foreach ($input_shopping_carts as $shopping_cart) {
            $shopping_carts []= new ShoppingCart($shopping_cart['goods_id'], $shopping_cart['count'] );
        }

        //根據購物車查商品。
        $goods_records =collect([]);
        foreach ( $shopping_carts as $shopping_cart ){
            $goods_records->push( Goods::findOrFail($shopping_cart['goods_id']) );
        }

        //根據購物車內容,及查出的商品價格,還有使用者已有的優惠券。去查詢最優的優惠券。
        $user_coupon_record = $this->find_user_coupon_from_shopping_cart(
            $shopping_carts, $goods_records ,$user_coupon_records
        );
        return $user_coupon_record;

    }


    /*
     * 根據購物車資訊,和已有的優惠券,查詢最優惠的一張優惠券。
     *
     * @param array $shopping_cart 前端傳來的商品資訊。
     * // 購物車格式
     * $shopping_cart = [
     *            [
     *                'goods_id' => 1,
     *                'count' => 3,
     *            ],
     *            ... ...
     *        ];
     *
     * @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_carts,Collection $goods_records,
                                                        Collection $user_coupon_records):array
    {
        $total = $this->calculate_total_price( $shopping_carts, $goods_records );
        $finded_user_coupon = $user_coupon_records
            ->filter(function ($user_coupon)use($total){
                return $user_coupon['condition_money'] < $total;
            })->map(function($user_coupon)use($total){
                if ($user_coupon['type']==1) {
                    $user_coupon['saved_money'] = $user_coupon['coupon_value'] ;
                }
                if ($user_coupon['type']==2) {
                    $user_coupon['saved_money'] = $total * (1- $user_coupon['coupon_value']) ;
                }
                return $user_coupon;
            })->sortByDesc(function ($user_coupon) {
                return $user_coupon['saved_money'];
            })->first();
        if ($finded_user_coupon) {
            return [
                 'saved_money' => $finded_user_coupon['saved_money'], //優惠券自身的等價金額
                 'user_coupon_record' => [
                     'id' => $finded_user_coupon['id'],
                     'type' => $finded_user_coupon['type'],
                     'coupon_value' => $finded_user_coupon['coupon_value'],
                     'coupon_name' => $finded_user_coupon['coupon_name'],
                 ]
              ];
        }
        return [
            'saved_money' => 0,
            'user_coupon_record' => null,
        ];
    }


    /**
     * 計算購物車總價。
     *
     * @param array $shopping_cart
     * @param Collection $goods_records
     * @return float
     * @throws \Exception
     */
    private function calculate_total_price(array $shopping_carts,Collection $goods_records):float
    {
        $total=0;
        foreach( $shopping_carts as $shop_cart){
            $count = $shop_cart['count'];
            $unit_price=0;
            foreach ( $goods_records as $goods ){
                if ( $goods['id'] == $shop_cart['goods_id'] ){
                    $unit_price = $goods['price'];
                    break;
                }
            }
            if (!$unit_price) {
                throw new \Exception('引數錯誤');
            }
            $total += $count * $unit_price;
        }
        return $total;
    }



}
購物車類
<?php
namespace App\Services\Order;

use arrayaccess;
use Exception;

/**
 * 購物車引數物件,與資料庫無關。
 *
 * Class ShoppingCart
 * @package App\Services\Order
 */
class ShoppingCart implements arrayaccess
{
    private $goods_id;
    private $count;

    public function __construct($goods_id, $count)
    {
        if (!$goods_id) {
            throw new Exception('入參商品id錯誤');
        }
        $count = intval($count);

        if (!$count) {
            throw new Exception('入參商品數量錯誤');
        }
        $this->goods_id=$goods_id;
        $this->count=$count;
    }

    public function offsetSet($offset, $value) {

    }
    public function offsetExists($offset) {

    }
    public function offsetUnset($offset) {

    }
    public function offsetGet($offset) {
        if (!in_array( $offset, ['goods_id', 'count'] )){
            throw new Exception('物件陣列化時呼叫鍵錯誤');
        }
        return $this->$offset;
    }
}

單元測試和整合測試一起測

./vendor/bin/phpunit 

會顯示測試透過。

總結:

1、大部分的情況下,單元測試透過後,需要再稍許檢查一下程式碼,看可讀性之類的是否合適。
2、本文也修改了控制器和 service 的程式碼,同時變數命名又做了調整。
3、因為此時測試程式碼都已寫好,所以可以放心大膽的改,改完後讓測試透過,就ok了。
4、個人比較喜歡用關聯陣列,所以實現此查詢優惠券功能的核心程式碼都用陣列,用物件也完全ok,就是物件 -> 屬性 這種方式,實際可能會更好一些,關係不大。
5、最後,這只是一個學習,真實程式碼的話,入參還需要檢查,以及單元測試至少得再加上優惠券為空的情況,本身就沒有,或者本身有優惠券但是一個符合條件的都沒有,這些都要測試程式碼有所體現。
6、這個實現的類中,有一個計算商品總價的方法,我沒有直接測試,如果願意,也可以修改這個方法為公有,然後進行測試。實際上,如果商品價格計算複雜的話,那麼,這是必要的。本文的例子是特別簡單的情況,所以不測。
7、編寫測試的好處我自己的感覺是,讓產品功能正確只是副產品,最主要的好處是寫程式碼之前已經開始思考了程式碼的層次和結構和引數這些情況,因為你知道你的程式碼需要透過測試。 而每次的測試透過都會不停提供信心,提供了正反饋,從而讓開發水平穩步提升。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章