关于单元测试一直是一个想写的话题,在真实的开发中,测试往往是容易被忽视或者说是被低估的环节。以前在友盟的时候,大约12年左右公司开始提倡写单元测试,也是从那个时候开始接触到了单元测试,可惜的是那会对测试了解的太少,公司也没有相关的培训,自己也是片面的理解单元测试,所拥有的相关知识也是从网上看到的只言片语,这些错误的认识导致的直接结果是 - 单元测试写不下去!
  当时有几个比较严重的误解:
  1、测试是对产品代码的补充
  2、单元测试即功能测试
  3、Android无法做单元测试
  在从业的这几年接触到的大部分研发同学所写的测试代码中也能看到这种误解。在交流的过程中,还有很多人是不写单元测试的,而借口都是一致的统一 - 太忙了!业务代码都还没有写完,哪有时间写单元测试呢?这种对测试的赤裸裸的歧视很大一部分原因是由于对测试的认识不正确 - 测试是对产品代码的补充,写测试是业务代码写完后空余时间干的事情。另外一种认识是TDD了,它认为测试时加快开发进度的,因为不断的跑TestCase可以让已经写下的产品逻辑更有保证,方便快速的Debug。这种争论在很多年前已经有了结论,这便是TDD - TestDriveDevelopment(测试驱动开发)。从TDD的角度来看,测试和产品代码是平等重要的,通常情况下测试代码和产品代码大约是1:1的关系。
  以上有用TDD来证明TDD的嫌疑
  第二个误解是把单元测试误做功能测试。什么是单元测试?单元测试英文是 "unit-test",对某个单元或功能模块的测试,具体的来说,单元测试是对某个类或者方法的测试。一个简单的例子比如:测试 boolean StringUtil.isEmpty(String str) 是否能能正常工作(判断输入字符串是否为空),那么我可以输入一些参数并通过返回值来得到预期的结果:
  assertTrue(StringUtil.isEmpty(null))
  assertTrue(StringUtil.isEmpty(""))
  assertFalse(StringUtil.isEmpty("any string"))
  ...
  这种简单的例子经常被大家拿来作为书写单元测试的示例,这个示例简单却不够好,因为一旦略微复杂的情况,大家把单元测试做成功能测试了。什么是功能测试呢?功能测试是从用户角度来观察的测试,测试系统中发生的一个行为,这个行为会导致所有的真实依赖都会被执行,而单元测试却要求Mock掉所有的依赖,让依赖变得可预期。比如下面的例子:
// 服务路由表,根据一个请求方法返回一个服务
public class ServiceTableImpl implements ServiceTable {
// 根据服务Id查询一个服务
private ClientFactory referenceClient;
// 访问数据库的DAO类,数据库保存了服务ID和请求方法的映射信息
private AppInfoDAO appInfoDAO;
@Override
public Service findService(String method) throws Exception {
if (method == null) {
logger.info("request method is null");
return null;
}
// 查询数据库得到ServiceID
AppInfo appInfo = appInfoDAO.getAppInfoByMethod(method);
if (appInfo == null || appInfo.getServiceId() == null) {
raiseException("service-id not found");
}
String serviceId = appInfo.getServiceId();
// 根据ServiceID查找服务
service = referenceClient.getService(serviceId);
if (service == null) {
raiseException("can't find service");
}
return service;
}
}
  注1:ServiceTablel 是一个请求方法路由服务,会按照请求的方法(method)返回对应的服务(Service)。
  注2:通常单元测试至少要提供两个TestCase来分别测试正确和错误的情况
  如果依然按照测试 boolean StringUtil.isEmpty(String str) 的方式来写测试代码会是这样的:
  ServiceTable servcieTable = new ServiceTableImpl();
  Service service = servcieTable.findService("a exist method");
  // expect not null
  assertNotNull(service);
  // expect exception
  Service service = servcieTable.findService("not exist method");
  fail();