替换实例化类的实现而不触及代码(java)

时间:2013-10-10 22:11:41

标签: java unit-testing constructor dependency-injection classloader

我有遗漏的代码,我不想碰。

public class LegacyCode{
    public LegacyCode() {
        Service s = new ClassA();
        s.getMessage();
    }
}

ClassA提供CORBA服务呼叫的地方。

public class ClassA implements Service{
    @Override
    public String getMessage() {
       // Make CORBA service call...
       return "Class A";
    }
}

界面Service看起来像;

public interface Service {
    String getMessage();
}

出于测试目的,我想用存根替换ServiceLegacyCode实现的ClassA)的实现。

public class ClassB implements Service {
    @Override
    public String getMessage() {
        return "Stub Class B";
    }
}

到目前为止一切顺利。但是,如果在显示的遗留代码中没有任何修改,可以在ClassB的实例化时加载ClassA而不是ClassA吗?

// In my test workbench
new LegacyCode(); // "Stub Class B"

我尝试编写自定义类加载器并在应用程序启动时通过java vm参数加载它,但只有第一个类(此处为LegacyCode)由该加载器加载。

提前致谢

3 个答案:

答案 0 :(得分:3)

使用PowerMock可以为构造函数代码创建模拟(或存根)。答案取自this link。我会尝试将其转换为与您的用例完全匹配:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassA.class)
public class LegacyTester {
    @Test
    public void testService() {
         // Inject your stub
         PowerMock.createMock(ClassA.class);
         Service stub = new MyServiceStub();
         PowerMock.expectNew(ClassA.class).andReturn(stub);
         PowerMock.replay(stub, ClassA.class);

         // Implement test logic here

         LegacyCode legacyCode = new LegacyCode();

         // Implement Test steps

        // Call verify if you want to make sure the ClassA constructor was called
        PowerMock.verify(stub, ClassA.class)
    }
}

这样,您可以在调用ClassA构造函数时注入存根,而无需更改旧代码。希望这就是你所需要的。

答案 1 :(得分:1)

AspectJ的一种方式。我同意Hovercraft,这是依赖注入的一个很好的例子,但如果你不能改变源代码,AspectJ可能是你的首选工具。

这比我更好地解释了AspectJ案例:Can AspectJ replace "new X" with "new SubclassOfX" in third-party library code?

答案 2 :(得分:0)

更简单的方法是在测试代码中创建ClassA。在最初在实际代码中找到的相同包中声明它并使用相同的方法名称。只是让方法什么也不做。

例如,如果您的项目结构如下所示:

+- src
  +- main
    +- java
      +- com.company.code
        -- LegacyCode.java
        -- Service.java
      +- com.company.code.service
        -- ClassA.java
  +- test
    +- java
      +- com.company.code
        -- LegacyCodeTest.java

ClassA下创建另一个/src/test/java/com/company/code/

package com.company.code.service;
/** Replaces the original implementation for testing purposes */
public class ClassA implements Service{
    @Override
    public String getMessage() {
       return "I replaced the original implementation";
    }
}

现在您的项目结构将如下所示:

+- src
  +- main
    +- java
      +- com.company.code
        -- LegacyCode.java
        -- Service.java
      +- com.company.code.service
        -- ClassA.java
  +- test
    +- java
      +- com.company.code
        -- LegacyCodeTest.java
      +- com.company.code.service
        -- ClassA.java

执行单元测试后,将加载ClassA文件夹中的test。这比使用模拟框架简单得多,特别是如果您的旧代码很乱,而不是依赖注入友好且实际的ClassA在任何地方都使用。

请记住,这种策略可能会使您的测试代码更加模糊,并且您无法测试ClassA本身的实际实现,也无法轻松地为不同的测试场景提供替代实现。 / p>