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

假设有五个类: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的问题,也就是一个类该怎样设计,才便于对它进行隔离的单元测试,但又不致于让这个类变得很丑陋?很想看看各位有何高见?

使用Ioc容器来测试可能适合一些。

这样,在你的CO中,就不要有: I c2 = new C2();
这样语句,直接引用C2的接口就可以。

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

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

是啊,如果把一个对象设计得便于应用IoC/DI的话,的确可以解决依赖查找问题。
这两天想了想,从OO的原则来说,如果C0依赖的业务对象有10个那么多的话,恐怕也违反对象的单一职责原则了,C0成所谓的“上帝对象”了。 :)
只是这个尺度的把握可真是......