单元测试讲究的是要把测试分布。类的职责要明晰,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() {...}
}
>>
楼主觉得这些内容需要如何做单元测试呢?

解决方案 »

  1.   

    小的不接招,你的意思是在大型系统中要合理设计使得可以进行单元测试?那就是说一个个的测下来?
    就象大哥举的例子,我怎么才能既测试ConfigLoader,又能测试 ConfigLoaderFactory 呢?
      

  2.   

    别客气,大家一起切磋学习。
    俺们来看看这个例子真实是如何使用的:
    <<
        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: 它的职责在于从配置文件加载并缓存到内存而已。
    所以单元测试就可以分布到这两项上面分别进行测试。只要两项单元测试都没有问题,就能够断言可以完成正确的配置加载功能。
      

  3.   

    ConfigLoaderFactory的单元测试相对简单,可以分为两项完成:
    (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的具体实现类,至于是何种具体实现类,在这个简单的需求内可以不用考虑。
      

  4.   

    而DefaultConfigLoader的单元测试就会相对复杂一点了。关键在于你难以做断言。为什么呢?因为DefaultConfigLoader读取配置文件config.xml,如果要做断言就必须依据config.xml的内容进行断言才能保证DefaultConfigLoader的功能实现是正确的。
    但是偏偏config.xml是一个用户可以修改内容的配置文件,当然不能限定用户配置什么内容。所以这个地方如果要做单元测试就一定要使用MOCK OBJECT的方式,使用一个测试专用的数据文件来进行单元测试。篇幅太长,就不多说了,楼主可以思考一下。
      

  5.   

    我们原来进行单元测试的时候有两种方式:
    1、如你所说的那种普通的单元测试;
    2、将有关联的单元测试用力打成一个 test suite进行大的测试;我们当时将一个子系统中300多个单元测试用力打成一个suite,这样就可以实现你所说的单元用力测试模块了。
      

  6.   

    还有比如我在一个大的工程中只负责一小段代码,我怎么才能初始化好所有的环境然后load的的代码或者我的测试呢?我的意思是运行的代码的先决条件怎么先构造起来?
      

  7.   

    你是说你连单元测试还不会,那么谈大型项目不就多余了。其实单元测试用例非常好构造,junit已经为你建立了测试框架。你只要按照使用你的方法的用户的调用过程建立测试用例就行了。
      

  8.   

    可是有些数据是从其它模块传输进来的,那么这些数据我是要自己模拟构造,还是将其它模块先load进来,然后产生数据?不好意思,班车马上开了,明天我继续来。谢谢。
      

  9.   

    数据你要使用MOCK OBJECT的方式来自己构造。