幸好 findService 方法还有一个返回值,可以让我们可以像 StringUtil.isEmpty 一样验证返回结果。为了测试正确的结果,我们还需要在数据库中提前插入一对method-service信息,并保证 ClientFactory 可以找到这个服务。同时还需要在 ClientFactory 和 AppInfoDAO 的实现代码里面打一些log,这样可以观察到查询服务和读数据库时候的一些信息,以保证这个测试的正常运行。测试错误情况比较简单,只需要随便给 findService 方法输入一个字符串参数,观察返回结果或者异常情况即可。这种测试是典型的功能测试,事实上,整个过程是在模拟一个正常的业务流程,但是这种测试时非常脆弱和不稳定的,ClientFactory 和 AppInfoDAO 内部发生的变化是不可预期的,再加上ServiceTableImpl 自身的逻辑,会导致整个行为可能性变化太大,输出结果不可预期。而且测试的范围也从原来的 ServiceTableImpl 扩展到 ServiceTableImpl + ClientFactory + AppInfoDAO。
  单元测试的目的在于测试一个局部的模块,在这里指的是 ServiceTableImpl,那么我们需要让 ClientFactory 和 AppInfoDAO的行为变得可预期,这样可以专心的测试ServiceTableImpl的内部逻辑,这才是单元测试!如何让 ClientFactory 和 AppInfoDAO 的行为变得可预期的呢,比如我需要让appInfoDAO.getAppInfoByMethod(method) 方法一定可以返回一个 serviceid, 让 referenceClient.getService(serviceId) 一定可以查找到一个服务呢?
  这用到了Mock技术 - 创建一个虚假的 AppInfoDAO 实现,让它返回需要的结果。对于上面的例子可以继承 AppInfoDAO 并覆盖它的 getAppInfoByMethod 方法:
  // 继承AppInfoDAO并覆盖getAppInfoByMethod方法,返回需要的结果
  public class MockAppInfoDAO extends AppInfoDAO {
  AppInfo getAppInfoByMethod(String method) {
  AppInfo appInfo = new AppInfo();
  appInfo.setServiceId("a service id");
  return appInfo;
  }
  }
  //把 MockAppInfoDAO 注入到ServiceTableImpl
  ServiceTableImpl.setAppInfoDAO(new MockAppInfoDAO());
  同样的道理可以Mock掉ClientFactory,如此便可以把 ServiceTableImpl 的依赖变得可控,这样可以单独的测试自己的内部逻辑了。
  对于第三点,我以前是非常坚持的 - Android很难做单元测试。Android确实很难做单元测试,尤其是Android是有界面的,有界面的系统往往都不容做单元测试。但是这并不对,测试不好做,往往是代码写的不够好并且没有用到合适的测试工具。先看一个简单的例子:
public class ShortLifeMemoCache<T> implements CacheService<T> {
private static final long DEFAULT_TTL = 1L*60*60*1000;
/**
* 生存时间(Time to live),单位:毫秒
*/
private long ttl = DEFAULT_TTL;
private final Map<String, Value<T>> cache =  new HashMap<String, Value<T>>();
@Override
public void put(String key, T t) {
long life = System.currentTimeMillis() + ttl;
cache.put(key, new Value<T>(t, life));
}
@Override
public T get(String key) {
Value<T> v = cache.get(key);
if (v != null){
if(v.getLife() > System.currentTimeMillis()) {
return v.getValue();
} else {
cache.remove(key);
}
}
return null;
}
@Override
public T remove(String key) {
return cache.remove(key).getValue();
}
@Override
public int size() {
return cache.size();
}
// 省略getter和setter方法
...
// 封装一个Value和一个时间戳,如果超时则丢弃
static class Value<T> {
// 生存时间
private long life;
// 真实的Value
private T t;
public Value() {}
public Value(T t, long life) {
setValue(t, life);
}
// 省略getter和setter方法
...
}
}
  注1:上面这个例子实现了一个内存缓存,被缓存的K-V对在内存中多存活一个小时,超出一个小时后会认为缓存过期并从内存中移除。
  注2:可能有同学会修改ttl值,把ttl设置成一个比较小的值,那么可以等待了,但是这样是非常脆弱的,处理临界值的时候容易出错