【问题标题】:Java Unit Test: Replace a private method under testJava单元测试:替换被测私有方法
【发布时间】:2010-01-07 12:33:53
【问题描述】:

在运行 JUnit 测试时,有什么方法可以替换私有方法中的逻辑?

一点背景知识:我们有一些私有方法与 OSGi 容器中的包交互。这在单元测试中不可用,因此方法将失败。

我们已经查看了 JMockIt,但方法替换功能似乎要强制您替换类中相互调用的所有方法。

实现是这样的:

public final doSomething() {  
    firstThing();
    secondThing();
}
private firstThing() {  
    // normal code
}
private secondThing() {  
    // code which is unavailable in a unit test
}

并且单元测试会指定 secondThing() 的新实现:

// replace secondThing() in impl with this secondThing()

private secondThing() {  
    // dummy code
}

// run tests

【问题讨论】:

    标签: unit-testing junit osgi


    【解决方案1】:

    您当然可以使用 JMockit 解决这种情况。 一种方法是定义一个“模拟”类,例如:

    public class MyTest
    {
        @Test
        public void testDoSomething()
        {
            new MockUp<ClassWhichDependsOnOtherBundles>()
            {
                @Mock
                void secondThing()
                {
                   // do anything here
                }
            };
    
            new ClassWhichDependsOnOtherBundles().doSomething();
        }
    }
    

    只有模拟类中的secondThing() 方法会被JMockit 替换。 JMockit Expectations API 也可以通过部分模拟来使用。

    【讨论】:

    • 这看起来很有趣。在某些情况下,我在 EasyMock 中错过了这一点,其中 afaik 只能模拟一个完整的类。
    • 是的,“JMockit Annotations”API 总是根据用户指定的 @Mock 方法进行部分模拟。 EasyMock API 类似于“JMockit Expectations”API;它们都支持部分模拟,尽管 EasyMock 需要在字符串中模拟方法的名称(即 partial 模拟)。
    • 我认为这最完全地回答了所提出的问题,尽管我很欣赏建议的解耦解决方案并且可能会重新设计
    【解决方案2】:

    您正在将您的实现与 osgi 对象的创建耦合(在 secondThing() 或类本身中执行它)。如果您从外部将实现传递给您的类,则可以在测试时使用存根/模拟。

    【讨论】:

    • 我也认为这是正确的解决方法。在我看来,任何与外界交互的组件都应该是通过构造函数/工厂参数(或其他可指定的)提供的对象,而不是硬编码到类中。 (显然,您必须有“叶子”才能触底,这些叶子本质上是指外部世界(例如服务器名称),但是您测试的方式不同。)然后,除这些叶子之外的程序的任何子部分都可以在完全内部进行测试受控宇宙。
    【解决方案3】:

    我也认为依赖注入可以解决这个问题。 如果你不想在你的项目中使用另一个框架,而这是唯一会出现问题的地方,你可以为 secondThing 定义一个接口并为此设置两个实现,一个用于原始代码,一个用于单元测试。

    【讨论】:

      【解决方案4】:

      我的建议 - 重新设计您的应用程序。如果你想改变 private 方法的行为:

      • 将其设为 protected / public 并在模拟对象中覆盖它
      • 将功能从方法中移到一个辅助类中,该类是可注入的(通过依赖注入)。然后模拟该助手并将模拟注入到被测类中,而不是原来的助手。

      一种解决方法可能是一些字节码操作技术,但我不建议这样做。

      【讨论】:

      • 那么,连java.lang.reflect.Proxy都不应该使用吗?它确实操纵字节码,你知道...
      【解决方案5】:

      有一个很好的存根模式

      ProductionClass.java:

      public class ProductionClass { ... //default visibility to make it available for stubbing void firstThing(){...} ... }

      BlaTest.java(与生产类相同的包):

      public class BlaTest { @Test void test_xx(){ //use stubbed impl ProductionClass sut = new ProductionClassStubbed(); sut.doSomething(); } } class ProductionClassStubbed extends ProductionClass{ @Override void firstThing(){ //stub out fill in what you want (like dummy behaviour) } }

      一个不同的东西。我在您的示例代码中看到了 final 修饰符。小心使用 final 修饰符。它们不利于可测试性。仅在确实需要时使用。

      【讨论】:

      • final关键字是Java中OO设计的重要机制。对于 JMockit 来说,它没有任何区别,所以尽可能多地使用它。
      • 是的,'final' 是一个 java 语言特性,它有它的用途。但对我来说,如果人们将它用作默认修饰符,这是一种反模式。我经常遇到一些情况,我必须根据遗留代码进行测试。 final 被使用了,我注定要存根。在我的情况下,mock-frameworks 不是正确的工具(-> 出于某些原因,我不得不构建自己的手动编码存根)。
      猜你喜欢
      • 2014-08-10
      • 1970-01-01
      • 1970-01-01
      • 2013-11-09
      • 1970-01-01
      • 2023-04-01
      • 2011-07-16
      相关资源
      最近更新 更多