Laravel 单元测试实战(4)- 测试成功后的方法重构并再次测试通过
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 协议》,转载必须注明作者和本文链接
推荐文章: