如何设计一个类,让它易于测试?

06-03-14 Santiago
假设有五个类:C0, C1, C2, C3, C4,它们都实现了接口I:

public interface I { 
  public void run(); 
}

C0根据业务要求会调用C1, C2, C3, C4的run()方法(这四个类的run()方法中都会进行数据库操作):

public class C0 implements I { 
  public void run() { 
    I c1 = new C1(); 
    c1.run(); 

    //... 
    //执行若干操作 
    //... 

    I c2 = new C2(); 
    c2.run(); 

    //... 
    //执行若干操作 
    //... 

    I c3 = new C3(); 
    c3.run(); 

    //... 
    //执行若干操作 
    //... 

    I c4 = new C4(); 
    c4.run(); 

    //... 
    //执行若干操作 
    //... 
  } 
}

在对C0的run()方法进行单元测试时发现了一个问题,C1~C4会进行数据库操作,比较费时,而且对它们的初始化都硬编码在C0的run()方法中了,所以编写C0的测试用例时无法用MockData来模拟C1~C4。

现在想到一个办法就是把C0改成下面这个样子:

public class C0 implements I { 

  private I c1; 
  private I c2; 
  private I c3; 
  private I c4; 

  public C0(I argC1, I argC2, I argC3, I argC4) { 
    c1 = argC1; 
    c2 = argC2; 
    c3 = argC3; 
    c4 = argC4; 
  } 

  public void run() { 
    c1.run(); 

    //... 
    //执行若干操作 
    //... 

    c2.run(); 

    //... 
    //执行若干操作 
    //... 

    c3.run(); 

    //... 
    //执行若干操作 
    //... 

    c4.run(); 

    //... 
    //执行若干操作 
    //... 
  } 

  public I getC1() { 
    return c1; 
  } 
   
  public void setC1(I argC1) { 
    c1 = argC1; 
  } 

  //... 
  //此处省略了c2~c4对应的getter/setter 
  //... 

}

这样的话,编写C0的测试用例时就可以使用jMock或EasyMock一类的工具来模拟C1~C4了。但如果还有C5, C6甚至是C10呢?那么在C0里可能会满是getter/setter,而且C0的构造器的参数列表也过长了。

本质上,我觉得这是个OOP的问题,也就是一个类该怎样设计,才便于对它进行隔离的单元测试,但又不致于让这个类变得很丑陋?很想看看各位有何高见?

    

banq
2006-03-15 09:52
使用Ioc容器来测试可能适合一些。

这样,在你的CO中,就不要有: I c2 = new C2();

这样语句,直接引用C2的接口就可以。

当你需要测试时,将CO需要引用的其他类都通过配置文件配置一下,这样,当你运行CO实例时,创建CO实例需要其他的类如C1 和C2 等类会被自动创建和引入CO实例。

当然这个Ioc容器最好是autowiring的,象picocontainer就不错。

Santiago
2006-03-16 11:48
是啊,如果把一个对象设计得便于应用IoC/DI的话,的确可以解决依赖查找问题。

这两天想了想,从OO的原则来说,如果C0依赖的业务对象有10个那么多的话,恐怕也违反对象的单一职责原则了,C0成所谓的“上帝对象”了。 :)

只是这个尺度的把握可真是......

猜你喜欢