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

Scala中如何正确为类方法编写单元测试并存根其他方法?优化多方法存根的实现方案

针对你的问题,我有几个更优雅的解决方案,既能隔离待测方法的依赖,又能避免参数过多导致的代码杂乱,同时也能更好地处理Scala Object的测试场景:

1. 最优方案:构造函数注入依赖特质

把需要存根的方法(b()、c()等)提取到一个独立的特质中,然后通过构造函数将这个特质的实例注入到待测类(SUTClass)中。这种方式可以集中管理所有依赖,新增方法时不需要修改待测类的方法参数列表。

重构后的待测代码

// 提取所有依赖方法到特质中
trait SUTDependencies {
  def b(): Unit
  def c(): Unit
  // 新增d()、e()时只需在这里添加方法定义
}

// 依赖的真实实现
class RealDependencies extends SUTDependencies {
  override def b(): Unit = println("Actual implementation of b()")
  override def c(): Unit = println("Actual implementation of c()")
}

// 待测类,通过构造函数注入依赖,默认使用真实实现
class SUTClass(deps: SUTDependencies = new RealDependencies) {
  def a(): Unit = {
    println("Hello from a()")
    deps.b()
    deps.c()
  }
}

测试用例

import org.mockito.MockitoSugar.{doNothing, mock, times, verify}
import org.scalatest.FlatSpec

class SUTClassTest extends FlatSpec {
  behavior of "a()"
  it should "call b() and c() exactly once without executing real implementations" in {
    // Mock依赖特质
    val mockDeps = mock[SUTDependencies]
    doNothing.when(mockDeps).b()
    doNothing.when(mockDeps).c()

    // 传入mock依赖创建SUT实例
    val sut = new SUTClass(mockDeps)
    sut.a()

    // 验证调用次数
    verify(mockDeps, times(1)).b()
    verify(mockDeps, times(1)).c()
  }
}

这种方案的优势非常明显:新增依赖方法时,只需要在SUTDependencies特质中添加定义,测试代码完全不需要修改,彻底解决了参数膨胀的问题。

2. 快速替代:使用Mockito Spy(部分Mock)

如果不想对原代码做大规模重构,Mockito的Spy功能可以帮你实现部分Mock:创建真实待测对象的Spy实例,然后存根你需要隔离的方法,同时保留其他方法的真实逻辑。

待测代码(无需修改)

class SUTClass {
  def a(): Unit = {
    println("Hello from a()")
    b()
    c()
  }
  def b(): Unit = println("Actual implementation of b()")
  def c(): Unit = println("Actual implementation of c()")
}

测试用例

import org.mockito.MockitoSugar.{doNothing, spy, times, verify}
import org.scalatest.FlatSpec

class SUTClassSpyTest extends FlatSpec {
  behavior of "a()"
  it should "call b() and c() exactly once, skipping real implementations" in {
    // 创建真实对象的Spy实例
    val sutSpy = spy(new SUTClass)

    // 存根b()和c(),避免执行真实逻辑
    doNothing.when(sutSpy).b()
    doNothing.when(sutSpy).c()

    // 执行待测方法
    sutSpy.a()

    // 验证调用次数
    verify(sutSpy, times(1)).b()
    verify(sutSpy, times(1)).c()
  }
}

这个方案适合快速验证逻辑,不需要改动原有代码结构,非常灵活。

3. Scala Object的更优测试方式

你提到的mockito-extensions全局配置确实不够理想(可能影响其他测试用例),更干净的做法是将Object中的业务逻辑提取到特质中,让Object继承这个特质,测试时直接Mock该特质即可。

重构后的Object代码

// 提取核心逻辑到特质
trait SUTLogic {
  def a(): Unit
  def b(): Unit
  def c(): Unit
}

// 原Object继承特质,仅作为单例入口
object SUTObject extends SUTLogic {
  override def a(): Unit = {
    println("Hello from a()")
    b()
    c()
  }
  override def b(): Unit = println("Actual implementation of b()")
  override def c(): Unit = println("Actual implementation of c()")
}

测试用例

import org.mockito.MockitoSugar.{doNothing, mock, times, verify}
import org.scalatest.FlatSpec

class SUTObjectTest extends FlatSpec {
  behavior of "SUTObject.a()"
  it should "call b() and c() exactly once" in {
    val mockLogic = mock[SUTLogic]
    doNothing.when(mockLogic).b()
    doNothing.when(mockLogic).c()

    // 如果需要验证a()的逻辑,也可以将a()的实现移到特质的可测试方法中
    // 或者进一步重构,让Object依赖特质实例,便于测试替换
    mockLogic.a()
    verify(mockLogic, times(1)).b()
    verify(mockLogic, times(1)).c()
  }
}

这种方式避免了直接Mock单例Object,既保证了测试隔离性,又符合Scala的面向特质编程理念。

总结

  • 优先选择构造函数注入依赖特质,这是最符合SOLID原则、最利于长期维护的方案;
  • 快速验证逻辑时可以用Mockito Spy,无需修改原代码;
  • 对于Scala Object,通过特质提取逻辑来避免全局配置,提升测试的可维护性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 16:37:44