单元测试讲究的是要把测试分布。类的职责要明晰,SRP(Single Resposibility Principle),这样才能对较大的系统作单元测试,需要重构的地方一定要重构使之可以单元测试。
比如,我举一个小小的例子来问问楼主,你看如何做单元测试?
<<
需要从一个配置文件config.xml中加载配置(配置文件是用户可以修改内容的),比如
<?xml version="1.0" encoding="UTF-8"?>
<config>
<host name="h1" ip="192.168.0.1" port="11111"/>
<host name="h2" ip="192.168.0.2" port="11112"/>
</config>
有一个数据结构可以描述这个配置信息:
public class Config {
private String name;
private String ip;
private String port;
}
定义了加载配置需要的业务接口:
public interface ConfigLoader {
public Config load(String name);
}
针对此接口作了具体的实现:
class DefaultConfigLoader implements ConfigLoader {
.... ....
}
辅助此接口的是一个工厂类(一般是单例),用它来产生ConfigLoader的实例:
public final class ConfigLoaderFactory {
public static ConfigLoaderFactory getInstance() {...}
public ConfigLoader getConfigLoader() {...}
}
>>
楼主觉得这些内容需要如何做单元测试呢?
比如,我举一个小小的例子来问问楼主,你看如何做单元测试?
<<
需要从一个配置文件config.xml中加载配置(配置文件是用户可以修改内容的),比如
<?xml version="1.0" encoding="UTF-8"?>
<config>
<host name="h1" ip="192.168.0.1" port="11111"/>
<host name="h2" ip="192.168.0.2" port="11112"/>
</config>
有一个数据结构可以描述这个配置信息:
public class Config {
private String name;
private String ip;
private String port;
}
定义了加载配置需要的业务接口:
public interface ConfigLoader {
public Config load(String name);
}
针对此接口作了具体的实现:
class DefaultConfigLoader implements ConfigLoader {
.... ....
}
辅助此接口的是一个工厂类(一般是单例),用它来产生ConfigLoader的实例:
public final class ConfigLoaderFactory {
public static ConfigLoaderFactory getInstance() {...}
public ConfigLoader getConfigLoader() {...}
}
>>
楼主觉得这些内容需要如何做单元测试呢?
就象大哥举的例子,我怎么才能既测试ConfigLoader,又能测试 ConfigLoaderFactory 呢?
俺们来看看这个例子真实是如何使用的:
<<
ConfigLoaderFactory factory = ConfigLoaderFactory.getInstance();
ConfigLoader configLoader = factory.getConfigLoader();
Config aConfig = configLoader.load("h1");
>>
类似于上面的写法,那么我们应该如何测试呢?直接向下面这样验证:
<<
assertEquals("192.168.0.1", aConfig.getHost());
assertEquals("11111", aConfig.getPort());
>>
显然并不好,为什么呢?因为这样的逻辑很复杂了,如果assert失败,你怎么能够精准地判断是在什么地方出现了异常而导致的?是工厂返回的实例错误?是加载的时候错误?(可能例子没有这么复杂,但是实际情况可能复杂程度远远超出预期)。因此,俺们需要把职责划分清楚,把单元测试分布到每个职责上面去。
还是以这个例子来说,我们可以区分出两项:
(1) ConfigLoaderFactory: 它的职责是创建一个ConfigLoader的实例,仅仅在于创建实例。
(2) DefaultConfigLoader: 它的职责在于从配置文件加载并缓存到内存而已。
所以单元测试就可以分布到这两项上面分别进行测试。只要两项单元测试都没有问题,就能够断言可以完成正确的配置加载功能。
(1) ConfigLoaderFactory作为一个单例(Singleton)类存在;
(2) getConfigLoader方法可以正确返回一个ConfigLoader的实例;
所以可以做成这样:
<<
public class TestConfigLoaderFactory extends TestCase {
public TestConfigLoaderFactory(String name) {super(name);} public void testSingelton() throws Exception {
ConfigLoaderFactory inst1 = ConfigLoaderFactory.getInstance();
ConfigLoaderFactory inst2 = ConfigLoaderFactory.getInstance();
assertNotNull(inst1);
assertNotNull(inst2);
assertSame(inst1, inst2); Class clazz = Class.forName("ConfigLoaderFactory");
assertNotNull(clazz);
assertEquals(0, clazz.getConstructor().length);
} public void testGetConfigLoader() {
ConfigLoaderFactory factory = ConfigLoaderFactory.getInstance();
assertNotNull(factory.getConfigLoader());
}
}
>>
经过这个单元测试,你一定可以确认Factory是单例类,并且invoke getConfigLoader方法可以保证返回一个ConfigLoader的具体实现类,至于是何种具体实现类,在这个简单的需求内可以不用考虑。
但是偏偏config.xml是一个用户可以修改内容的配置文件,当然不能限定用户配置什么内容。所以这个地方如果要做单元测试就一定要使用MOCK OBJECT的方式,使用一个测试专用的数据文件来进行单元测试。篇幅太长,就不多说了,楼主可以思考一下。
1、如你所说的那种普通的单元测试;
2、将有关联的单元测试用力打成一个 test suite进行大的测试;我们当时将一个子系统中300多个单元测试用力打成一个suite,这样就可以实现你所说的单元用力测试模块了。