代购系统单元测试实战:用PHPUnit/TestNG搭建可回归的测试体系
导读:代购系统的业务逻辑复杂,采购链路长。任何一个环节出错都可能导致经济损失。如何用自动化测试保障系统稳定性?本文分享taocarts在单元测试、集成测试和端到端测试方面的实践经验。
一、代购系统的测试困境
跨境代购系统的测试往往是个被忽略的方向。很多开发团队认为先把功能上线才是第一位的,回头再补测试用例,结果到最后也没有补上。但当系统的业务模块越来越多(商品采集、1688代采、订单状态流转、汇率转换、物流轨迹推送),改一行代码都可能影响多个模块,没有自动化测试兜底的代码库会变得像纸糊的房子——别人碰一下就碎一个角,改一个bug引入三个新bug。
代购系统测试的主要难点有几个:外部依赖多导致单元测试难以隔离——商品采集模块强依赖1688的API,物流模块依赖外部追踪接口,这些外部系统的网络波动和限流会直接影响测试结果的确定性。状态流转复杂导致回归用例庞大——一个订单从创建到签收可能经历十几个状态变化,正向流程和异常流程都需要覆盖。汇率计算精度问题依赖算——浮点运算的边缘case可能需要用Decimal类型反复验证才能保证不漏。库存扣减的并发竞争条件难以模拟——分布式场景下的超卖问题,在单机调试模式下几乎不会触发,只有高并发压测才能发现问题。
taocarts的测试体系采用测试金字塔模型,底座单元测试用PHPUnit或者TestNG覆盖核心业务函数和算法逻辑,中间集成测试针对服务间的接口交互用MockServer验证,顶部端到端测试用Codeception或Selenium模拟用户核心流程的完整操作路径。
二、单元测试实践
2.1 汇率计算模块的单元测试
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Services\CurrencyConverter;
use Mockery;
class CurrencyConverterTest extends TestCase
{
protected $converter;
protected function setUp(): void
{
parent::setUp();
// Mock Redis依赖
$redisMock = Mockery::mock('Illuminate\Redis\RedisManager');
$redisMock->shouldReceive('get')
->with('fx:rate:CNY:USD')
->andReturn('0.137');
$redisMock->shouldReceive('setex')
->andReturn(true);
$this->converter = new CurrencyConverter($redisMock);
}
/** @test */
public function it_converts_cny_to_usd_correctly()
{
$amountCny = 100;
$expectedUsd = 13.7;
$actual = $this->converter->convert($amountCny, 'CNY', 'USD');
$this->assertEquals($expectedUsd, $actual);
}
/** @test */
public function it_handles_same_currency_conversion()
{
$amount = 100;
$actual = $this->converter->convert($amount, 'USD', 'USD');
$this->assertEquals($amount, $actual);
}
/** @test */
public function it_applies_rate_buffer_for_display()
{
$amountCny = 100;
$displayAmount = $this->converter->convertForDisplay($amountCny, 'USD', 'test_user');
// 含2%缓冲,应稍高于市场汇率
$this->assertGreaterThan(13.7, $displayAmount);
$this->assertLessThan(14.0, $displayAmount);
}
/** @test */
public function it_handles_zero_amount_conversion()
{
$actual = $this->converter->convert(0, 'CNY', 'USD');
$this->assertEquals(0, $actual);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
}
2.2 订单状态机的单元测试
订单状态流转的逻辑,通过测试用例验证状态转移的合法性。
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\DaigouOrder;
use App\Services\OrderStateMachine;
use App\Exceptions\InvalidStateTransitionException;
class OrderStateMachineTest extends TestCase
{
/** @test */
public function it_allows_valid_state_transitions()
{
$order = DaigouOrder::factory()->create(['status' => 'pending']);
$stateMachine = new OrderStateMachine($order);
// 允许: pending -> paid
$stateMachine->transitionTo('paid');
$this->assertEquals('paid', $order->fresh()->status);
// 允许: paid -> purchased
$stateMachine->transitionTo('purchased');
$this->assertEquals('purchased', $order->fresh()->status);
// 允许: purchased -> shipped
$stateMachine->transitionTo('shipped');
$this->assertEquals('shipped', $order->fresh()->status);
}
/** @test */
public function it_prevents_invalid_state_transitions()
{
$order = DaigouOrder::factory()->create(['status' => 'pending']);
$stateMachine = new OrderStateMachine($order);
// 不允许: pending -> shipped (跳过支付和采购)
$this->expectException(InvalidStateTransitionException::class);
$stateMachine->transitionTo('shipped');
}
/** @test */
public function it_prevents_transition_to_already_current_state()
{
$order = DaigouOrder::factory()->create(['status' => 'pending']);
$stateMachine = new OrderStateMachine($order);
$this->expectException(InvalidStateTransitionException::class);
$stateMachine->transitionTo('pending');
}
/** @test */
public function it_only_allows_specific_events_to_trigger_transitions()
{
$order = DaigouOrder::factory()->create(['status' => 'pending']);
$stateMachine = new OrderStateMachine($order);
// 通过事件触发
$stateMachine->trigger('payment_success');
$this->assertEquals('paid', $order->fresh()->status);
// 不允许的事件
$this->expectException(InvalidStateTransitionException::class);
$stateMachine->trigger('invalid_event');
}
}
三、集成测试与端到端测试
集成测试主要解决服务间的接口验证问题。在taocarts的设计中,集成测试覆盖了订单创建后支付服务正确调用了1688的采购接口,物流状态更新时正确触发了邮件通知,汇率更新任务正常从第三方拉取了数据这几个方向。使用TestContainers启动真实的MySQL和Redis容器来模拟生产环境,而不是用内存数据库去做测试——内存数据库的行为和真实数据库在某些事务隔离级别的表现上存在差异,可能掩盖真正的bug。
端到端测试模拟真实用户的使用场景是最后一道防线。taocarts使用Laravel Dusk或Cypress来模拟用户完成一次完整的反向海淘流程:从商品浏览开始,加入购物车、提交订单、完成支付(Mock支付网关)、等待1688自动采购、物流状态更新到最终确认收货。
更多推荐
所有评论(0)