31

我正在尝试监视一个对象,并且我想在构造函数调用它之前对构造函数调用的方法进行存根。
我的课看起来像这样:

public class MyClass {
    public MyClass() {
         setup();
    }

    public void setup() {

    }
}

不得调用 setup 方法。那么,我如何监视这个方法(和存根设置,使其什么都不做)?
它可以很好地模拟该方法,但我想进行单元测试MyClass,所以我需要其他方法。


需要对 setup 方法进行存根以使其不执行任何操作的原因:
我正在编写乐高机器人 (lejos),并且我在 setup 中放置了一些机器人需要工作的代码。但是,当我在 TinyVM(安装在机器人上的 VM)之外调用它时,java 崩溃,因为它没有正确初始化 VM(因为测试在我的 PC 上运行)。对于单元测试,设置并不重要。
我不能存根类/方法设置调用,因为其中一些是公共静态最终变量。

4

4 回答 4

15

要直接回答您的问题,您不能使用 Mockito 来存根从构造函数调用的方法。在开始模拟之前,Mockito 需要一个类的实例,而且您还没有为自己提供创建用于测试的实例的方法。

更一般地,正如有效 Java 第17 条中所述,您不应该从构造函数中调用可覆盖的方法。例如,如果这样做,您可以在引用final字段但在设置字段之前运行的子类中提供覆盖final。它可能不会给您带来麻烦,但在 Java 中这是一个坏习惯。

幸运的是,您可以非常轻松地重构代码:

public class MyClass {
  public MyClass() {
    this(true);
  }

  /** For testing. */
  MyClass(boolean runSetup) {
    if (runSetup) {
      setup();
    }
  }

  /* ... */
}

为了更明显,您可以将单参数MyClass构造函数设为私有,并提供public static工厂方法:

/* ... */
  public static MyClass createForTesting() {
    return new MyClass(false);
  }

  private MyClass(boolean runSetup) {
/* ... */

尽管一些开发人员认为在主要用于测试的方法中编写任何代码是一种不好的做法,但请记住,您负责代码的设计,而测试是您绝对知道需要适应的少数消费者之一. 尽管在“生产”代码中避免显式测试设置仍然是一个好主意,但为了测试而创建额外的方法或重载通常会使您的代码整体上更干净,并且可以大大提高您的测试覆盖率和可读性。

于 2014-05-12T15:37:22.527 回答
12
  1. 使用 PowerMock。

  2. 导入库后,将其设置为使用不得调用的实例方法来操作要模拟的类。

像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
  1. 在测试开始时禁止该方法。

像这样:

suppress(method(Myclass.class, "setup"));
  1. 在测试中根据需要自定义 setup() 方法的行为。

像这样:

doAnswer(new Answer<Void>() {
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
           // code here
           return null;
      }
 }).when(Myclass.class, "setup");
于 2014-10-22T02:48:40.733 回答
11

感谢您的建议,但它有点太复杂了。
我最终通过扩展类并覆盖我的设置方法来模拟该方法。这样默认构造函数就不会调用它的 setup 实现,而是调用被覆盖的方法。
这是代码:

// src/author/MyClass.java

public class MyClass {
    public MyClass() {
        setup();
    }

    protected void setup() {
        throw new Exception("I hate unit testing !");
    }

    public boolean doesItWork() {
        return true;
    }
}

// test/author/MyClass.java

public class MyClassTest {
    private class MockedMyClass extends MyClass {
        @Override
        protected void setup() {

        }
    }

    private MyClass instance;

    @Before
    public void setUp() { // Not to be confusing with `MyClass#setup()`!
        instance = new MockedMyClass();
    }

    @Test
    public void test_doesItWork() {
        assertTrue(instance.doesItWork());
    }

}

如果您不希望 MyTest 的 setup 方法被除您的测试之外的其他子类调用或覆盖(因为其他开发人员可能会通过使用 setup 方法将事情搞砸),只需将可见性更改为默认值,只有您的类能够调用设置。


如果有更简单的方法,请回答问题,因为我对我的解决方案不是 100% 满意。

于 2014-05-12T12:21:05.543 回答
0

@fragorl 的答案适用于静态类方法,但在实例方法上失败。如果您需要模拟/间谍实例方法,而不是抑制做替换:

PowerMockito.replace(MemberMatcher.method(Myclass.class, "setup")).with(new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] arguments) throws Throwable {
                //add some special behaviour
                //if needed can call original method afterwards in that way:
                //method.invoke(o, arguments);
                return null;
            }
        });
于 2021-09-06T09:52:58.783 回答