正 文

J2EE中使用Spring AOP框架和EJB组件


www.7dspace.com  更新日期:2006-2-14 17:52:15  七度空间


  测试通知代码

  您可能注意到了,代码不依赖于TradeDao或YahooFeed。这样就可以使用模仿对象完全独立地测试这个组件。模仿对象测试方法允许在组件执行之前声明期望,然后验证这些期望在组件调用期间是否得到满足。要了解有关模仿测试的更多信息,请参见 “参考资料”部分。下面我们将会使用jMock框架,该框架提供了一个灵活且功能强大的API来声明期望。

  测试和实际的应用程序使用相同的Spring bean配置是个不错的主意,但是对于特定组件的测试来说,不能使用实际的依赖性,因为这会破坏组件的孤立性。然而,Spring允许在创建Spring 的应用程序上下文时指定一个BeanPostProcessor,从而置换选中的bean和依赖性。在这个例子中,可以使用模仿对象的一个Map,这些模仿对象是在测试代码中创建的,用于置换Spring配置中的bean:

public class StubPostProcessor implements BeanPostProcessor {
  private final Map stubs; 

  public StubPostProcessor( Map stubs) {
    this.stubs = stubs;
  } 

  public Object postProcessBeforeInitialization(Object bean, String beanName) {
    if(stubs.containsKey(beanName)) return stubs.get(beanName);
    return bean;
  } 

  public Object postProcessAfterInitialization(Object bean, String beanName) {
    return bean;
  }

}

  在测试用例类的setUp()方法中,我们将使用baseTradeManager和yahooFeed组件的模仿对象来初始化 StubPostProcessor,而这两个组件是使用jMock API创建的。然后,我们就可以创建ClassPathXmlApplicationContext(配置其使用BeanPostProcessor)来实例化一个tradeManager组件。产生的tradeManager组件将使用模仿后的依赖性。

  这种方法不仅允许孤立要测试的组件,还可以确保在Spring bean配置中正确定义通知。实际上,要在不模拟大量容器基础架构的情况下使用这样的方法来测试在EJB组件中实现的业务逻辑是不可能的:

public class ForeignTradeAdviceTest extends TestCase {
  TradeManager tradeManager;
  private Mock baseTradeManagerMock;
  private Mock yahooFeedMock; 

  protected void setUp() throws Exception {
    super.setUp(); 

    baseTradeManagerMock = new Mock(TradeManager.class, "baseTradeManager");
    TradeManager baseTradeManager = (TradeManager) baseTradeManagerMock.proxy(); 

    yahooFeedMock = new Mock(TradeManager.class, "yahooFeed");
    TradeManager yahooFeed = (TradeManager) yahooFeedMock.proxy(); 

    Map stubs = new HashMap();
    stubs.put("yahooFeed", yahooFeed);
    stubs.put("baseTradeManager", baseTradeManager); 

    ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(CTX_NAME);
    ctx.getBeanFactory().addBeanPostProcessor(new StubPostProcessor(stubs)); 

    tradeManager = (TradeManager) proxyFactory.getProxy();
  }
  ...

  在实际的testAdvice()方法中,可以为模仿对象指定期望并验证(例如)baseTradeManager上的getPrice()方法是否返回null,然后yahooFeed上的getPrice()方法也将被调用:

public void testAdvice() throws Throwable {
    String symbol = "testSymbol";
    BigDecimal expectedPrice = new BigDecimal("0.222"); 

    baseTradeManagerMock.expects(new InvokeOnceMatcher()).method("getPrice")
      .with(new IsEqual(symbol)).will(new ReturnStub(null)); 

    yahooFeedMock.expects(new InvokeOnceMatcher()).method("getPrice")
      .with(new IsEqual(symbol)).will(new ReturnStub(expectedPrice)); 

    BigDecimal price = tradeManager.getPrice(symbol);
    assertEquals("Invalid price", expectedPrice, price);
        baseTradeManagerMock.verify();
    yahooFeedMock.verify();
  }

  这段代码使用jMock约束来指定, baseTradeManagerMock期望只使用一个等于symbol的参数调用getPrice()方法一次,而且这次调用将返回null。类似地,yahooFeedMock也期望对同一方法只调用一次,但是返回expectedPrice。这允许在setUp()方法中运行所创建的 tradeManager组件,并断言返回的结果。

  这个测试用例很容易参数化,从而涵盖所有可能的用例。注意,当组件抛出异常时,可以很容易地声明期望。

测试 baseTradeManager yahooFeed 期望
调用 返回 抛出 调用 返回 抛出 结果t 异常
1 true 0.22 - false - - 0.22 -
2 true - e1 false - - - e1
3 true null - true 0.33 - 0.33 -
4 true null - true null - null -
5 true null - true - e2 - e2

  可以使用这个表更新测试类,使其使用一个涵盖了所有可能场景的参数化序列:

... 

  public static TestSuite suite() {
    BigDecimal v1 = new BigDecimal("0.22");
    BigDecimal v2 = new BigDecimal("0.33"); 

    RuntimeException e1 = new RuntimeException("e1");
    RuntimeException e2 = new RuntimeException("e2"); 

    TestSuite suite = new TestSuite(ForeignTradeAdviceTest.class.getName());
    suite.addTest(new ForeignTradeAdviceTest(true, v1,   null, false, null, null, v1,   null));
    suite.addTest(new ForeignTradeAdviceTest(true, null, e1,   false, null, null, null, e1));
    suite.addTest(new ForeignTradeAdviceTest(true, null, null, true,  v2,   null, v2,   null));
    suite.addTest(new ForeignTradeAdviceTest(true, null, null, true,  null, null, null, null));
    suite.addTest(new ForeignTradeAdviceTest(true, null, null, true,  null, e2,   null, e2));
    return suite;
  } 

  public ForeignTradeAdviceTest(
      boolean baseCall, BigDecimal baseValue, Throwable baseException,
      boolean yahooCall, BigDecimal yahooValue, Throwable yahooException,
      BigDecimal expectedValue, Throwable expectedException) {
    super("test"); 

    this.baseCall = baseCall;
    this.baseWill = baseException==null ? 
        (Stub) new ReturnStub(baseValue) : new ThrowStub(baseException);
    this.yahooCall = yahooCall;
    this.yahooWill = yahooException==null ? 
        (Stub) new ReturnStub(yahooValue) : new ThrowStub(yahooException);
    this.expectedValue = expectedValue;
    this.expectedException = expectedException;
  } 

  public void test() throws Throwable {
    String symbol = "testSymbol"; 

    if(baseCall) {
      baseTradeManagerMock.expects(new InvokeOnceMatcher())
        .method("getPrice").with(new IsEqual(symbol)).will(baseWill);
    } 

    if(yahooCall) {
      yahooFeedMock.expects(new InvokeOnceMatcher())
        .method("getPrice").with(new IsEqual(symbol)).will(yahooWill);
    } 

    try {
      BigDecimal price = tradeManager.getPrice(symbol);
      assertEquals("Invalid price", expectedValue, price);
    } catch(Exception e) {
      if(expectedException==null) {
        throw e;
      }
    }
        baseTradeManagerMock.verify();
    yahooFeedMock.verify();
  } 

  public String getName() {
    return super.getName()+" "+
      baseCalled+" "+baseValue+" "+baseException+" "+
      yahooCalled+" "+yahooValue+" "+yahooException+" "+
      expectedValue+" "+expectedException;
  }
  ...

  在更复杂的情况下,上面的测试方法可以很容易地扩展为大得多的输入参数集合,而且它仍然会立刻运行且易于管理。此外,把所有参数移入一个外部配置文件或者甚至Excel电子表格是合理的做法,这些配置文件或电子表格可以由QA团队管理,或者直接根据需求生成。

5页,页码:[1] [2] [3] [4] [5] 

上一篇:情人节个性礼品光盘制作全攻略
下一篇:Alexa排名能帮你赚钱么?
标题:J2EE中使用Spring AOP框架和EJB组件 作者:Eugene Kuleshov 来源:BEA
收藏此页】【打印】【关闭
站 内 搜 索
 

热 点 导 读
特 别 推 荐