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

如何使用JUnit测试包含for循环的saveService方法?

如何测试药品计划保存与删除服务?

问题背景

我目前只会测试JPA的planDayRepo.save(planDay)部分,不知道如何测试包含业务逻辑的savePlans方法,以及另一个类似的、通过循环更新pillNumber的删除服务,希望得到测试建议。

待测试的savePlans方法

public String savePlans(List<PlanDay> plans) {
    for (int i = 0; i < plans.size(); i++) {
        PlanDay planDay = plans.get(i);
        long medsId = planDay.getMeds().getMedsId();
        int medsToTake = planDay.getMedsToTake();
        int pillNumber = medsRepo.getReferenceById(medsId).getPillNumber();
        int pillUpdate = pillNumber - medsToTake;
        Meds updatePlanDay = medsRepo.findById(medsId).get();
        if (pillUpdate > 0) {
            updatePlanDay.setPillNumber(pillUpdate);
        } else {
            return "Error: No pills available (Existing Pills: " + pillNumber + ")";
        }
        planDayRepo.save(planDay);
    }

    return "Saved Successfully";
}

实体类代码

Meds 实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "meds")
@JsonIgnoreProperties(value = { "days" })
public class Meds {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long medsId;

    @Column
    private String medsName;

    @Column
    private BigDecimal price;

    @Column
    private String category;

    @Column
    private int pillNumber;

    @Column
    private Date medsDate;

    @OneToMany(mappedBy = "meds", cascade = {CascadeType.REMOVE}, fetch = FetchType.LAZY)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Set<PlanDay> days = new HashSet<PlanDay>();

}

Plans 实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "plans")
@JsonIgnoreProperties(value = { "days" })
public class Plans {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long planId;

    @OneToMany(mappedBy = "plans", fetch = FetchType.LAZY)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Set<PlanDay> days = new HashSet<PlanDay>();
}

PlanDay 实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "planDay")
@JsonIgnoreProperties(value = { "planDaysId" })
public class PlanDay {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long planDaysId;

    @Column
    @PastOrPresent
    @FutureOrPresent(groups = OneWeekInTheFuture.class)
    @Min(value = 2022 - 12 - 22, groups = OneWeekInTheFuture.class)
    @Max(value = 2022 - 12 - 29, groups = OneWeekInTheFuture.class)
    private Date planDate;

    @Column
    private String weekday;

    @ManyToOne
    @JoinColumn(name = "planId", nullable = false)
    private Plans plans;

    @ManyToOne
    @JoinColumn(name = "medsId", nullable = false)
    private Meds meds;

    @Column
    private int medsToTake;
}

控制器代码

@PostMapping(value = "/plan/save")
public String savePlans(@RequestBody List<PlanDay> plans) {
    return saveService.savePlans(plans);
}

测试建议

一、单元测试(Mockito 模拟依赖)

单元测试聚焦业务逻辑,无需真实数据库,用Mockito模拟medsRepo和planDayRepo:

1. 正常场景:单个PlanDay,药品数量充足

  • 步骤:
    1. 构造PlanDay对象,关联一个Meds(比如medsId=1,pillNumber=10),设置medsToTake=3
    2. 模拟medsRepo.getReferenceById(1)返回该Meds
    3. 模拟medsRepo.findById(1)返回该Meds
    4. 调用savePlans方法
  • 验证:
    • 返回值为"Saved Successfully"
    • Meds的pillNumber被更新为7
    • planDayRepo.save()被调用1次
    • medsRepo的相关方法调用次数符合预期

2. 正常场景:多个PlanDay,药品均充足

  • 构造2个PlanDay,分别关联不同或相同的Meds(比如一个关联pillNumber=10的药品,取3片;另一个关联pillNumber=20的药品,取5片)
  • 模拟对应medsRepo的方法返回
  • 验证:
    • 返回成功信息
    • 所有关联的Meds的pillNumber都正确更新
    • 每个PlanDay都被保存

3. 异常场景:药品数量不足

  • 构造PlanDay,关联的Meds的pillNumber=2,medsToTake=3
  • 模拟medsRepo的方法返回该Meds
  • 验证:
    • 返回错误信息"Error: No pills available (Existing Pills: 2)"
    • planDayRepo.save()未被调用
    • Meds的pillNumber未被修改

4. 异常场景:药品不存在

  • 构造PlanDay关联不存在的medsId=999
  • 模拟medsRepo.getReferenceById(999)抛出EntityNotFoundException,或者medsRepo.findById(999)返回Optional.empty()(此时调用get()会抛出NoSuchElementException)
  • 验证方法是否正确处理该异常(如果代码未加捕获,需测试异常抛出情况,或考虑在业务代码中增加异常处理)

二、集成测试(真实数据库验证)

集成测试验证完整流程,用嵌入式数据库(如H2)模拟真实环境:

1. 准备测试数据

  • 测试前插入测试用的Meds和Plans数据,比如插入Meds(medsId=1, pillNumber=10)、Plans(planId=1)

2. 测试正常保存

  • 构造List<PlanDay>,包含一个关联medsId=1和planId=1的PlanDay,medsToTake=3
  • 调用控制器的/plan/save接口,或直接调用saveService.savePlans()
  • 验证:
    • 返回成功信息
    • 查询数据库,Meds的pillNumber变为7
    • PlanDay表中存在这条记录

3. 测试中途失败的情况

  • 构造2个PlanDay:第一个关联pillNumber=10的药品,取3片;第二个关联pillNumber=2的药品,取3片
  • 调用方法后,验证返回错误信息
  • 注意:当前代码无事务管理,第一个PlanDay会被保存,Meds的数量也会被更新,这是潜在问题。测试时可验证该行为,后续可考虑给方法添加@Transactional注解保证原子性。

三、删除服务的测试思路

对于类似的、通过循环更新pillNumber的删除服务,测试逻辑和savePlans一致:

  • 正常场景:删除单个/多个PlanDay,验证对应的Meds的pillNumber正确增加(比如删除一个取了3片的记录,药品数量加3),且PlanDay被从数据库中移除
  • 异常场景:删除不存在的PlanDay,验证方法是否正确处理(比如无操作、返回提示)
  • 批量删除:删除多个关联不同药品的PlanDay,验证所有药品的数量都正确更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:10:25