重构静态方法/静态字段以进行测试

时间:2023-02-05 14:15:27

I have the following legacy code:

我有以下遗留代码:

public class MyLegacyClass
{
    private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource"

    public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj)
    {
       // do stuff using jndiName
    }
}

This class is working in a J2EE-Container.

这个类在J2EE-Container中工作。

Now I would like to test the class outside of the container.

现在我想测试容器外面的类。

What is the best strategy? Refactoring is basically allowed.

什么是最好的策略?基本上允许重构。

Accessing the LegacyDataSource is allowed (the test does not have to be a "pure" unit-test).

允许访问LegacyDataSource(测试不必是“纯”单元测试)。

EDIT: Introducing additional runtime-frameworks is not allowed.

编辑:不允许引入其他运行时框架。

3 个解决方案

#1


7  

Just to make @Robin's suggestion of a strategy pattern more concrete: (Notice that the public API of your original question remains unchanged.)

只是为了让@ Robin建议更具体的策略模式:(请注意,原始问题的公共API保持不变。)

public class MyLegacyClass {

  private static Strategy strategy = new JNDIStrategy();

  public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj) {
    // legacy logic
    SomeLegacyClass result = strategy.doSomeStuff(legacyObj);
    // more legacy logic
    return result;
  }

  static void setStrategy(Strategy strategy){
    MyLegacyClass.strategy = strategy;
  }

}

interface Strategy{
  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj);
}

class JNDIStrategy implements Strategy {
  private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource";

  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
    // do stuff using jndiName
  }
}

...and JUnit test. I'm not a big fan of having to do this setup/teardown maintenance, but that's an unfortunate side effect of having an API based on static methods (or Singletons for that matter). What I do like about this test is it does not use JNDI - that's good because (a) it'll run fast, and (b) the unit test should only be testing the business logic in doSomeLegacyStuff() method anyway, not testing the actual data source. (By the way, this assumes the test class is in the same package as MyLegacyClass.)

...和JUnit测试。我不是必须做这种设置/拆卸维护的忠实粉丝,但这是一个令人遗憾的副作用,即使用基于静态方法的API(或者Singletons就此而言)。我不喜欢这个测试是它不使用JNDI - 这是很好的,因为(a)它会跑得快,和(b)单元测试应该只无论如何测试在doSomeLegacyStuff()方法中的业务逻辑,而不是测试实际数据来源。 (顺便说一下,这假设测试类与MyLegacyClass在同一个包中。)

public class MyLegacyClassTest extends TestCase {

  private MockStrategy mockStrategy = new MockStrategy();

  protected void setUp() throws Exception {
    MyLegacyClass.setStrategy(mockStrategy);
  }

  protected void tearDown() throws Exception {
    // TODO, reset original strategy on MyLegacyClass...
  }

  public void testDoSomeLegacyStuff() {
    MyLegacyClass.doSomeLegacyStuff(..);
    assertTrue(..);
  }

  static class MockStrategy implements Strategy{

    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
      // mock behavior however you want, record state however
      // you'd like for test asserts.  Good frameworks like Mockito exist
      // to help create mocks
    }
  }
}

#2


2  

Refactor the code to use dependency injection. Then use you preferred DI framework (Spring, Guice, ...) to inject your resources. That will make it easy to switch between resource objects and strategies at runtime.

重构代码以使用依赖注入。然后使用您首选的DI框架(Spring,Guice,...)来注入您的资源。这样可以在运行时轻松切换资源对象和策略。

In this case, you can inject your datasource.

在这种情况下,您可以注入数据源。

EDIT: Based on your new restriction, you can accomplish the same thing by using a strategy pattern to set your datasource at runtime. You can probably just use a properties file to distinguish which strategy to create and supply the datasource. This would require no new framework, you would just be hand coding the same basic functionality. We used this exact idea with a ServiceLocator to supply a mock datasource when testing outside of the Java EE container.

编辑:根据您的新限制,您可以通过使用策略模式在运行时设置数据源来完成相同的任务。您可以只使用属性文件来区分要创建和提供数据源的策略。这将不需要新的框架,您只需手动编码相同的基本功能。我们使用ServiceLocator这个确切的想法在Java EE容器外部进行测试时提供模拟数据源。

#3


1  

I think that the best solution here is bind that JNDI to a local

我认为这里最好的解决方案是将JNDI绑定到本地

The legacy Code is using the jndiName like that:

遗留代码正在使用jndiName:

DataSource datasource = (DataSource)initialContext.lookup(DATASOURCE_CONTEXT);

So, The solution here is bind a local (or whatever you have for you test data) into a JNDI like that:

所以,这里的解决方案是将本地(或任何你拥有的测试数据)绑定到JNDI中,如下所示:

  BasicDataSource dataSource = new BasicDataSource();
  dataSource.setDriverClassName(System.getProperty("driverClassName"));
  dataSource.setUser("username");
  dataSource.setPassword("password");
  dataSource.setServerName("localhost");
  dataSource.setPort(3306);
  dataSource.setDatabaseName("databasename");

And then the binding:

然后绑定:

Context context = new InitialContext();
context.bind("java:comp/env/jdbc/LegacyDataSource",datasource); 

Or something similar, hope that helps you.

或类似的东西,希望能帮到你。

Good luck!

#1


7  

Just to make @Robin's suggestion of a strategy pattern more concrete: (Notice that the public API of your original question remains unchanged.)

只是为了让@ Robin建议更具体的策略模式:(请注意,原始问题的公共API保持不变。)

public class MyLegacyClass {

  private static Strategy strategy = new JNDIStrategy();

  public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj) {
    // legacy logic
    SomeLegacyClass result = strategy.doSomeStuff(legacyObj);
    // more legacy logic
    return result;
  }

  static void setStrategy(Strategy strategy){
    MyLegacyClass.strategy = strategy;
  }

}

interface Strategy{
  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj);
}

class JNDIStrategy implements Strategy {
  private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource";

  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
    // do stuff using jndiName
  }
}

...and JUnit test. I'm not a big fan of having to do this setup/teardown maintenance, but that's an unfortunate side effect of having an API based on static methods (or Singletons for that matter). What I do like about this test is it does not use JNDI - that's good because (a) it'll run fast, and (b) the unit test should only be testing the business logic in doSomeLegacyStuff() method anyway, not testing the actual data source. (By the way, this assumes the test class is in the same package as MyLegacyClass.)

...和JUnit测试。我不是必须做这种设置/拆卸维护的忠实粉丝,但这是一个令人遗憾的副作用,即使用基于静态方法的API(或者Singletons就此而言)。我不喜欢这个测试是它不使用JNDI - 这是很好的,因为(a)它会跑得快,和(b)单元测试应该只无论如何测试在doSomeLegacyStuff()方法中的业务逻辑,而不是测试实际数据来源。 (顺便说一下,这假设测试类与MyLegacyClass在同一个包中。)

public class MyLegacyClassTest extends TestCase {

  private MockStrategy mockStrategy = new MockStrategy();

  protected void setUp() throws Exception {
    MyLegacyClass.setStrategy(mockStrategy);
  }

  protected void tearDown() throws Exception {
    // TODO, reset original strategy on MyLegacyClass...
  }

  public void testDoSomeLegacyStuff() {
    MyLegacyClass.doSomeLegacyStuff(..);
    assertTrue(..);
  }

  static class MockStrategy implements Strategy{

    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
      // mock behavior however you want, record state however
      // you'd like for test asserts.  Good frameworks like Mockito exist
      // to help create mocks
    }
  }
}

#2


2  

Refactor the code to use dependency injection. Then use you preferred DI framework (Spring, Guice, ...) to inject your resources. That will make it easy to switch between resource objects and strategies at runtime.

重构代码以使用依赖注入。然后使用您首选的DI框架(Spring,Guice,...)来注入您的资源。这样可以在运行时轻松切换资源对象和策略。

In this case, you can inject your datasource.

在这种情况下,您可以注入数据源。

EDIT: Based on your new restriction, you can accomplish the same thing by using a strategy pattern to set your datasource at runtime. You can probably just use a properties file to distinguish which strategy to create and supply the datasource. This would require no new framework, you would just be hand coding the same basic functionality. We used this exact idea with a ServiceLocator to supply a mock datasource when testing outside of the Java EE container.

编辑:根据您的新限制,您可以通过使用策略模式在运行时设置数据源来完成相同的任务。您可以只使用属性文件来区分要创建和提供数据源的策略。这将不需要新的框架,您只需手动编码相同的基本功能。我们使用ServiceLocator这个确切的想法在Java EE容器外部进行测试时提供模拟数据源。

#3


1  

I think that the best solution here is bind that JNDI to a local

我认为这里最好的解决方案是将JNDI绑定到本地

The legacy Code is using the jndiName like that:

遗留代码正在使用jndiName:

DataSource datasource = (DataSource)initialContext.lookup(DATASOURCE_CONTEXT);

So, The solution here is bind a local (or whatever you have for you test data) into a JNDI like that:

所以,这里的解决方案是将本地(或任何你拥有的测试数据)绑定到JNDI中,如下所示:

  BasicDataSource dataSource = new BasicDataSource();
  dataSource.setDriverClassName(System.getProperty("driverClassName"));
  dataSource.setUser("username");
  dataSource.setPassword("password");
  dataSource.setServerName("localhost");
  dataSource.setPort(3306);
  dataSource.setDatabaseName("databasename");

And then the binding:

然后绑定:

Context context = new InitialContext();
context.bind("java:comp/env/jdbc/LegacyDataSource",datasource); 

Or something similar, hope that helps you.

或类似的东西,希望能帮到你。

Good luck!