【问题标题】:change private method behaviour for a unit test更改单元测试的私有方法行为
【发布时间】:2014-03-25 04:00:38
【问题描述】:

这不是我的代码的样子,但它会接近我想要的示例

class Phone {
    public makeCall(String number) {
        addEntryToCallLog(number)
        //eventually make a call
    }

    private void addEntryToCallLog(String number) {
        //make database calls
    }
}


class PhoneTest {
    Phone phone
    @Test
    public void canMakeACall() {
        //mock the behaviour of phone.addEntryToCallLog to prevent database exceptions
        phone.makeCall("1223");
        //Assert somehow that call was made << --IGNORE this NOT IMPORTANT
    }
}

我想在单元测试中测试“makeCall”,但是当我这样做时,代码会抛出一些数据库异常,从而破坏我的测试。 我认为能够测试模拟 java 私有方法的行为是合理的,因为这允许所有测试的行为一致。

我过去使用过 groovy,通过它我可以使用 spock 来模拟私有方法的行为。我也可以使用元类对我创建的实例做同样的事情。但是在 java 中似乎没有直接的方法来做到这一点。

我也尝试过 mockito 和 power mockito,但它们允许我更改私有方法的返回值,但它们不允许我更改行为。

这似乎是一个显而易见的事情,有人已经处理过。我想我错过了一些东西。但它是什么。

【问题讨论】:

  • Mockito 模拟调用,默认情况下,除了返回您指定的值外,根本没有任何行为。这应该做你想做的,因为不会进行数据库调用。

标签: java unit-testing mocking mockito junit4


【解决方案1】:

我的建议是将可见性从private 放宽到包私有,可能带有@VisibleForTesting annotation/** Visible for testing. */ 注释。那时,您可以使用doAnswer“部分模拟”您的内部方法,正如 DTing 在另一个答案中提到的那样,以替换实际实现的行为。

这里的主要问题是 Mockito 最擅长将接口转换为存根实现。然而,私有方法故意不是接口的一部分,因此 Java 通过普通 Mockito 所依赖的一些反射特性使它们成为harder to access。 (在幕后,Mockito 正在创建您正在模拟的类的子类,子类通常不能覆盖私有方法。)

您还应该记住,理想情况下,您的单元测试应该将您的类作为一个单元进行测试,而不需要深入研究实现细节。通过说您试图替换私有方法的行为,您表示您的类需要一个尚不存在的接缝。你可以考虑重构依赖注入:

class Phone {
  interface CallLogger {
    void addEntry(String number);
  }

  private final CallLogger logger;

  public Phone() {
    this(new DefaultCallLogger());
  }

  /** Visible for testing. */
  Phone(CallLogger logger) {
    this.logger = logger;
  }

  /* ... */
}

...然后您可以选择所需的任何 CallLogger,包括在生产中访问数据库、在测试中伪造或模拟它,或者将来用批处理版本替换它。同样用于制作addEntryToCallLog package-private 或protected:您表示子类可以更改它们将条目添加到调用日志的方式,这正是您在测试中尝试做的,并承认这将有助于Java VM 和您的代码的读者了解行为在哪些点可以改变。


话虽如此,如果您只是想使用 PowerMockito 来替换实现,您可以使用 PowerMockito 的 doAnswerwhen 为您的私有方法提供不同的行为。请记住,您有机会改进设计并花费更少的时间和精力来处理实现细节。

【讨论】:

    【解决方案2】:

    您的示例很奇怪,因为您的 getContactFromDatabase 不返回任何内容,但您使用它来设置 Contact 变量。如果您想模拟对象创建行为,请查看以下内容:

    https://code.google.com/p/mockito/wiki/MockingObjectCreation

    class PhoneTest {
    
      @Spy Phone phone;
    
      @Test
      public void canMakeCall() {
        doReturn(someContact)
          .when(phone)
          .getContactFromDatabase(someString);
    
        phone.makeCall(someString);
      }   
    }
    

    如果你什么都不想做:

    class PhoneTest {
    
      @Spy Phone phone;
    
      @Test
      public void canMakeCall() {
        doNothing()
          .when(phone)
          .getContactFromDatabase(someString);
    
        phone.makeCall(someString);
      }   
    }
    

    http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#13

    您可以创建真实对象的间谍。当您使用间谍时, 调用真正的方法(除非方法被存根)。真正的间谍 应谨慎使用,偶尔使用,例如在交易时 使用遗留代码。

    【讨论】:

    • 我已将问题修改为明智。有什么想法吗?
    • 还要注意 addEntryToCallLog 是私有的
    • 如果你有更具体的例子,我可以看看,但你问的很抽象。 doNothing 应该足够了,如果你想确保addEntryToCallLog(number) 被调用,你可以使用verify(phone).addEntryToCallLog(number)
    猜你喜欢
    • 2014-08-10
    • 2011-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    相关资源
    最近更新 更多