active
This commit is contained in:
@@ -1,227 +0,0 @@
|
||||
---
|
||||
name: 异步事件单元测试
|
||||
overview: 结合 slot_console 现有 Free Credits 测试实践,说明异步 MQ 事件应分层测试:不直连 RabbitMQ,优先测 Logic 编排,再补 Event 解析与 EventBus 路由;并给出可新增的示例用例结构。
|
||||
todos:
|
||||
- id: entity-test
|
||||
content: 新增 FreeCreditInitEntityTest:字段映射与 resolveWalletAmount 回退
|
||||
status: completed
|
||||
- id: bus-factory
|
||||
content: 新增 tests/Support/ConsoleBusMessageFactory 统一构造 MQBusEntity 载荷
|
||||
status: completed
|
||||
- id: extend-logic-tests
|
||||
content: 扩展 FreeCreditsHandleFreeCreditInitTest:幂等、无配置、异常路径
|
||||
status: completed
|
||||
- id: optional-event-inject
|
||||
content: (可选)FreeCreditInitEvent 注入 Logic + Event 单测
|
||||
status: completed
|
||||
- id: optional-eventbus-nack
|
||||
content: (可选)EventBus deal 单测:TYPE_FREE_CREDIT_INIT 成功 ack / 失败 nack
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 异步事件单元测试写法(slot_console)
|
||||
|
||||
## 当前架构
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Wallet as slot_wallet
|
||||
participant MQ as RabbitMQ_console_bus
|
||||
participant Bus as EventBus_deal
|
||||
participant Ev as FreeCreditInitEvent
|
||||
participant Logic as FreeCreditsLogic
|
||||
|
||||
Wallet->>MQ: JSON uid/type/data
|
||||
MQ->>Bus: AMQPMessage
|
||||
Bus->>Ev: case TYPE_FREE_CREDIT_INIT
|
||||
Ev->>Ev: FreeCreditInitEntity
|
||||
Ev->>Logic: handleFreeCreditInit
|
||||
Logic->>Logic: freezeFirstRecharge + wallet RPC
|
||||
```
|
||||
|
||||
**结论**:异步只发生在 MQ 传输层;单测应测 **消息解析 → 路由 → 业务编排**,而不是启动真实 `event:bus` 或 RabbitMQ。
|
||||
|
||||
---
|
||||
|
||||
## 项目里已有的做法(推荐延续)
|
||||
|
||||
### 1. 单元测试:直接测 Logic(主路径)
|
||||
|
||||
现有 [`tests/Unit/FreeCreditsHandleFreeCreditInitTest.php`](/Users/ray/Documents/project/www/slot/slot_console/tests/Unit/FreeCreditsHandleFreeCreditInitTest.php) 已覆盖 `free_credit_init` 的**业务结果**,等价于事件消费后的效果:
|
||||
|
||||
- [`FreeCreditsLogicHarness`](/Users/ray/Documents/project/www/slot/slot_console/tests/Support/FreeCreditsLogicHarness.php):注入 `userContext` / `activeConfig` / `walletService`
|
||||
- [`RecordingWalletService`](/Users/ray/Documents/project/www/slot/slot_console/tests/Support/RecordingWalletService.php):记录 `freeCreditsFreeze`,不发 HTTP
|
||||
- [`FreeCreditsConfigStub`](/Users/ray/Documents/project/www/slot/slot_console/tests/Support/FreeCreditsConfigStub.php):活动配置桩
|
||||
|
||||
断言示例(已有):
|
||||
|
||||
- 定格金额 = `balance_before_qf`
|
||||
- `biz_id` = `free_credits_freeze:{orderId}`
|
||||
- `balance_before_qf <= 0` 时不调用 freeze
|
||||
|
||||
**运行**:
|
||||
|
||||
```bash
|
||||
cd slot_console && ./vendor/bin/phpunit --testsuite Unit
|
||||
```
|
||||
|
||||
提现三类事件同理:[`tests/Integration/FreeCreditsLogicCashoutResultTest.php`](/Users/ray/Documents/project/www/slot/slot_console/tests/Integration/FreeCreditsLogicCashoutResultTest.php) 直接调 `handleFirstCashoutResult`,不经过 EventBus。
|
||||
|
||||
### 2. 集成测试:需要 DB 时
|
||||
|
||||
[`FreeCreditsDbTestCase`](/Users/ray/Documents/project/www/slot/slot_console/tests/Integration/FreeCreditsDbTestCase.php):`RUN_DB_TESTS=1` 才跑,默认事务回滚。
|
||||
|
||||
```bash
|
||||
RUN_DB_TESTS=1 ./vendor/bin/phpunit --testsuite Integration --filter FreeCredits
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 建议的分层(按投入产出排序)
|
||||
|
||||
| 层级 | 测什么 | 是否必需 | 依赖 |
|
||||
|------|--------|----------|------|
|
||||
| A. Entity | `FreeCreditInitEntity` 字段映射、`resolveWalletAmount()` 回退 | 推荐 | 无 |
|
||||
| B. Logic | `handleFreeCreditInit` / `handleFirstCashoutResult` 编排 | **已有,继续扩展** | Harness + RecordingWallet |
|
||||
| C. Event | `FreeCreditInitEvent::handle` 把 `MQBusEntity` 转成 Logic 参数 | 可选 | 需可注入 Logic(见下) |
|
||||
| D. EventBus | `deal()` 按 `type` 分发、失败 nack | 少量即可 | Mock `AMQPMessage` |
|
||||
| E. E2E | 真 MQ + wallet 发消息 | 手工/脚本,非单测 | 环境 |
|
||||
|
||||
**原则**:B 覆盖 90% 风险;A 防 payload 字段错;C/D 防「路由写错 type」和「实体解析漏字段」。
|
||||
|
||||
---
|
||||
|
||||
## 可新增的测试示例
|
||||
|
||||
### A. Entity 单测(纯数组 → 实体)
|
||||
|
||||
新建 `tests/Unit/FreeCreditInitEntityTest.php`:
|
||||
|
||||
```php
|
||||
$entity = new FreeCreditInitEntity([
|
||||
'balance_before_qf' => 15000,
|
||||
'wallet_amount' => 0,
|
||||
'amount' => 5000,
|
||||
'biz_id' => 'order_1',
|
||||
]);
|
||||
$this->assertSame(15000, $entity->balance_before_qf);
|
||||
$this->assertSame(5000, $entity->resolveWalletAmount()); // wallet_amount 为 0 时回退 amount
|
||||
```
|
||||
|
||||
对应生产代码:[`app/entity/mq/FreeCreditInitEntity.php`](/Users/ray/Documents/project/www/slot/slot_console/app/entity/mq/FreeCreditInitEntity.php)。
|
||||
|
||||
### B. 构造 MQ 载荷辅助方法
|
||||
|
||||
在 `tests/Support/` 增加工厂,统一模拟 wallet 发出的 body:
|
||||
|
||||
```php
|
||||
public static function consoleBusMessage(int $uid, string $type, array $data): MQBusEntity
|
||||
{
|
||||
return new MQBusEntity(['uid' => $uid, 'type' => $type, 'data' => $data]);
|
||||
}
|
||||
|
||||
// free_credit_init 示例
|
||||
public static function freeCreditInitBus(int $uid, int $balanceBefore, int $walletAmount, string $bizId): MQBusEntity
|
||||
{
|
||||
return self::consoleBusMessage($uid, MQBusEntity::TYPE_FREE_CREDIT_INIT, [
|
||||
'balance_before_qf' => $balanceBefore,
|
||||
'wallet_amount' => $walletAmount,
|
||||
'biz_id' => $bizId,
|
||||
'source' => 'test',
|
||||
'currency' => 'INR',
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
与 [`WalletLogic::sendConsoleBus`](/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) 字段保持一致。
|
||||
|
||||
### C. Event 层单测(当前结构的限制)
|
||||
|
||||
[`FreeCreditInitEvent`](/Users/ray/Documents/project/www/slot/slot_console/app/command/event/FreeCreditInitEvent.php) 内部写死 `new FreeCreditsLogic()`,**无法在不改代码的情况下 mock Logic**。
|
||||
|
||||
两种做法(二选一):
|
||||
|
||||
1. **小重构(推荐)**:Event 构造函数注入 `FreeCreditsLogic`,单测传入 `FreeCreditsLogicHarness`。
|
||||
2. **不测 Event**:认为 Event 只有 5 行胶水,由 B 层保证;仅加 A 层测 data 解析。
|
||||
|
||||
若采用 1,示例:
|
||||
|
||||
```php
|
||||
$bus = ConsoleBusMessageFactory::freeCreditInitBus($uid, 15000, 5000, 'order_1');
|
||||
$harness = (new FreeCreditsLogicHarness())->inject($context, $config, $wallet);
|
||||
(new FreeCreditInitEvent($harness))->handle($bus);
|
||||
$this->assertCount(1, $wallet->freezeCalls);
|
||||
```
|
||||
|
||||
### D. EventBus 路由单测(少量)
|
||||
|
||||
Mock `PhpAmqpLib\Message\AMQPMessage`:
|
||||
|
||||
```php
|
||||
$message = $this->createMock(AMQPMessage::class);
|
||||
$message->method('getBody')->willReturn(json_encode([
|
||||
'uid' => 90088002,
|
||||
'type' => MQBusEntity::TYPE_FREE_CREDIT_INIT,
|
||||
'data' => ['balance_before_qf' => 15000, 'wallet_amount' => 5000, 'biz_id' => 'x'],
|
||||
]));
|
||||
$message->expects($this->once())->method('ack'); // 成功应 ack
|
||||
|
||||
$bus = new EventBus();
|
||||
// 若 Logic 仍 new 在 Event 内,需配合 C 的注入或接受集成测
|
||||
$bus->deal($message);
|
||||
```
|
||||
|
||||
**nack 场景**([`EventBus.php` L164-167](/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php)):Logic 抛异常时,`TYPE_FREE_CREDIT_INIT` 应 `nack(true)` 且不 `ack`。可让 Harness 的 `freeCreditsFreeze` 抛异常,断言 `$message->expects($this->once())->method('nack')->with(true)`。
|
||||
|
||||
注意:`EventBus` 依赖多,路由测试宜 **只测 switch 分支 + ack/nack**,业务细节仍放 B。
|
||||
|
||||
---
|
||||
|
||||
## 四个 Free Credits 相关 type 怎么测
|
||||
|
||||
| MQ type(常量) | 单测重点 | 现有覆盖 |
|
||||
|-----------------|----------|----------|
|
||||
| `TYPE_FREE_CREDIT_INIT` | 定格金额、biz_id、幂等跳过 | `FreeCreditsHandleFreeCreditInitTest` |
|
||||
| `TYPE_FREE_CREDITS_FIRST_CASHOUT_SUCCESS` | 档位 completed、player 状态 | `FreeCreditsLogicCashoutResultTest` |
|
||||
| `TYPE_FREE_CREDITS_FIRST_CASHOUT_FAIL` | 失败回滚逻辑 | 可补 case |
|
||||
| `TYPE_FREE_CREDITS_FIRST_CASHOUT_REJECTED` | rejected → ready | 已有 reject case |
|
||||
|
||||
EventBus 里前三类目前 **直接调 Logic**(未走独立 Event 类),单测继续打 Logic 即可;常量定义在 [`MQBusEntity`](/Users/ray/Documents/project/www/slot/slot_console/app/entity/mq/MQBusEntity.php)。
|
||||
|
||||
---
|
||||
|
||||
## 不建议在单测里做的
|
||||
|
||||
- 启动 `php webman event:bus` 或连接真实 RabbitMQ
|
||||
- 跨服务联调 wallet → console(留给脚本如 `scripts/run_free_credits_first_freeze.php` 或手工验收)
|
||||
- 在单测里依赖 `UserTagService::getByUid`(用 Harness 注入 context)
|
||||
|
||||
---
|
||||
|
||||
## 推荐落地顺序(若你要补测试)
|
||||
|
||||
1. 补 `FreeCreditInitEntityTest`(A)
|
||||
2. 补 `ConsoleBusMessageFactory`(B 辅助)
|
||||
3. 扩展 `FreeCreditsHandleFreeCreditInitTest`:无活动配置、已定格幂等、Logic 抛错
|
||||
4. (可选)`FreeCreditInitEvent` 注入 Logic + Event 单测(C)
|
||||
5. (可选)`EventBusFreeCreditInitTest` 只测 ack/nack(D)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph unit [Unit 无 MQ]
|
||||
E[EntityTest]
|
||||
L[LogicHarnessTest]
|
||||
end
|
||||
subgraph optional [Optional]
|
||||
Ev[EventTest]
|
||||
Bus[EventBusRoutingTest]
|
||||
end
|
||||
subgraph integration [Integration RUN_DB_TESTS]
|
||||
DB[DbTestCase]
|
||||
end
|
||||
E --> L
|
||||
L --> Ev
|
||||
Ev --> Bus
|
||||
L --> DB
|
||||
```
|
||||
Reference in New Issue
Block a user