You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

应用层服务测试要点?附业务服务代码求单元测试方案

应用层服务的单元测试策略与实践

我完全理解你的困惑——这种纯编排领域逻辑的应用层服务看起来没什么实质性逻辑可测,但其实它的核心职责就是正确协调领域层和基础设施层的依赖,所以单元测试还是有明确的目标的。结合你给出的execute方法,我来拆解一下这类服务的测试要点和实践方法:

一、应用层服务需要测试的核心内容

这类服务的单元测试不用关注领域层的业务逻辑(那是领域对象单元测试的责任),重点要验证的是编排逻辑的正确性:

  • 依赖交互的正确性:确保各个依赖(仓储、领域对象)被调用的顺序、次数、参数完全符合预期
  • 异常传递的正确性:验证领域层或仓储抛出的异常(比如StaffNotFound、WrongCargoReleaseCode等)能被正确向上传递,没有被意外捕获或丢失
  • 返回值的合规性:确认方法返回了符合要求的DTO实例,类型和结构正确
  • 领域行为的触发:确保领域对象的核心方法(比如staff->releaseCargo)被正确调用,应用层完成了触发领域逻辑的职责

二、针对这类服务编写单元测试的步骤与示例

以PHPUnit为例,这类测试的核心是用Mock隔离所有外部依赖,只验证被测服务的编排逻辑:

1. 成功场景测试示例

public function testExecuteSuccessfullyTriggersCargoRelease()
{
    // 1. 准备测试数据与DTO
    $staffIdString = 'staff-1001';
    $releaseCodeString = 'REL-2024-001';
    $request = new ReleaseItemRequestDTO();
    $request->authenticatedStaffId = $staffIdString;
    $request->releaseCode = $releaseCodeString;

    $staffId = StaffEntityId::from($staffIdString);
    $releaseCode = ReleaseCode::from($releaseCodeString);

    // 2. 创建Mock依赖
    $mockStaff = $this->createMock(Staff::class);
    $mockCargo = $this->createMock(Cargo::class);

    $mockStaffRepo = $this->createMock(StaffRepository::class);
    $mockStaffRepo->method('findById')
        ->with($staffId)
        ->willReturn($mockStaff);

    $mockCargoRepo = $this->createMock(CargoRepository::class);
    $mockCargoRepo->method('findByReleaseCode')
        ->with($releaseCode)
        ->willReturn($mockCargo);

    // 3. 设置预期的交互行为
    // 断言领域方法被正确调用
    $mockStaff->expects($this->once())
        ->method('releaseCargo')
        ->with($mockCargo, $releaseCode);

    // 断言仓储的save方法被调用
    $mockCargoRepo->expects($this->once())
        ->method('save')
        ->with($mockCargo);

    // 4. 执行被测服务
    $releaseService = new YourReleaseItemService($mockStaffRepo, $mockCargoRepo);
    $response = $releaseService->execute($request);

    // 5. 断言返回值
    $this->assertInstanceOf(AcknowledgmentResponseDTO::class, $response);
}

2. 异常场景测试示例(以StaffNotFound为例)

public function testExecuteThrowsStaffNotFoundWhenStaffDoesNotExist()
{
    // 1. 准备测试数据
    $nonExistentStaffId = 'staff-9999';
    $request = new ReleaseItemRequestDTO();
    $request->authenticatedStaffId = $nonExistentStaffId;
    $request->releaseCode = 'REL-2024-001';

    $staffId = StaffEntityId::from($nonExistentStaffId);

    // 2. Mock仓储抛出异常
    $mockStaffRepo = $this->createMock(StaffRepository::class);
    $mockStaffRepo->method('findById')
        ->with($staffId)
        ->willThrowException(new StaffNotFound());

    $mockCargoRepo = $this->createMock(CargoRepository::class);

    // 3. 执行并断言异常
    $releaseService = new YourReleaseItemService($mockStaffRepo, $mockCargoRepo);
    $this->expectException(StaffNotFound::class);
    $releaseService->execute($request);
}

3. 其他异常场景的测试思路

对于CargoNotFound、WrongCargoReleaseCode等异常,测试逻辑类似:

  • Mock对应的依赖抛出目标异常
  • 执行execute方法,断言方法会抛出对应的异常
  • 确保其他依赖的方法不会被意外调用(比如如果Staff不存在,就不应该调用cargoRepository的方法)

三、总结

这类应用层服务的单元测试核心是验证编排的准确性:你不需要测试releaseCargo里的业务逻辑(那是Staff类的单元测试要做的),只需要确保应用层正确地调用了这个方法,传递了正确的参数,并且正确处理了依赖返回的结果和异常。这样的测试既能保证应用层的逻辑正确性,又不会和领域层的测试重复。

内容的提问来源于stack exchange,提问作者a4w

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:37:43