概述
单测是晋升软件质量的有力手段。然而,由于编程语言上的支持不力,以及一些欠好的编程习惯,导致编写单测很坚苦。
最容易领略最容易编写的单测,莫过于独立函数的单测。所谓独立函数,就是只依赖于传入的参数,不修改任何外部状态的函数。指定输入,就能确定地输出相应的功效。运行任意次,都是一样的。在函数式编程中,有一个出格的术语:“引用透明性”,也就是说,可以利用函数的返回值彻底地替代函数挪用自己。独立函数常见于东西类及东西要领。
不外,现实经常没有这么优美。应用要读取外部设置,要依赖外部处事获取数据举办处理惩罚等,导致应用好像无法纯真地“通过牢靠输入获得牢靠输出”。实际上,有两种要领可以尽大概断绝外部依赖,使得依赖于外部情况的工具要领回归“独立函数”的原味。
(1) 引用外部变量的函数, 将外部变量转化为函数参数; 修改外部变量的函数,将外部变量转化为返回值或返回工具的属性。
(2) 借助函数接口以及lambda表达式,断绝外部处事。
断绝依赖设置
先看一段代码。这段代码通过Spring读取已有处事器列表设置,并随机选取一个作为上传处事器。
public class FileService { // ... @Value("${file.server}") private String fileServer; /** * 随机选取上传处事器 * @return 上传处事器URL */ private String pickUrl(){ String urlStr = fileServer; String[] urlArr = urlStr.split(","); int idx = rand.nextInt(2); return urlArr[idx].trim(); } }
咋一看,这段代码也没什么差池。但是,当编写单测的时候,就难过了。 这段代码引用了实例类FileService的实例变量 fileServer ,而这个是从设置文件读取的。要编写单测,得模仿整个应用启动,将相应的设置读取进去。但是,这段代码无非就是从列表随机选取处事器罢了,并不需要涉及这么巨大的进程。这就是导致编写单测坚苦的原因之一:轻率地引用外部实例变量或状态,使得原来纯粹的函数或要领变得不那么“纯粹”了。
要更容易地编写单测,就要尽大概消除函数中引用的外部变量,将其转化为函数参数。进一步地,这个要领实际上跟 FileService 没什么扳连,反倒更像是随机东西要领。应该写在 RandomUtil 里,而不是 FileService。 以下代码显示了改革后的功效:
public class RandomUtil { private RandomUtil() {} private static Random rand = new Random(47); public static String getRandomServer(String servers) { if (StringUtils.isBlank(servers)) { throw new ExportException("No server configurated."); } String[] urlArr = servers.split(","); int idx = rand.nextInt(2); return urlArr[idx].trim(); } } private String pickUrl(){ return RandomUtil.getRandomServer(fileServer); }
public class RandomUtilTest { @Test public void testGetRandomServer() { try { RandomUtil.getRandomServer(""); fail("Not Throw Exception"); } catch (ExportException ee) { Assert.assertEquals("No server configurated.", ee.getMessage()); } String servers = "uploadServer1,uploadServer2"; Set<String> serverSet = new HashSet<>(Arrays.asList("uploadServer1", "uploadServer2")); for (int i=0; i<100;i++) { String server = RandomUtil.getRandomServer(servers); Assert.assertTrue(serverSet.contains(server)); } } }
这样的代码并不鲜见。 引用实例类中的实例变量或状态,是面向工具编程中的常见做法。然而,昆山软件开发,尽量面向工具是一种优秀的宏观工程理念,在代码处理惩罚上,却不足细致。而我们只要尽大概将引用实例变量的要领酿成含实例变量参数的要领,就能让单测更容易编写。
断绝依赖处事
先看代码。这是一段很常见的分页代码。按照一个查询条件,获取工具列表和总数,返回给前端。
@RequestMapping(value = "/searchForSelect") @ResponseBody public Map<String, Object> searchForSelect(@RequestParam(value = "k", required = false) String title, @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "rows", defaultValue = "10") Integer pageSize) { CreativeQuery query = new CreativeQuery(); query.setTitle(title); query.setPageNum(page); query.setPageSize(pageSize); List<CreativeDO> creativeDTOs = creativeService.search(query); Integer total = creativeService.count(query); Map<String, Object> map = new HashMap<String, Object>(); map.put("rows", (null == creativeDTOs) ? new ArrayList<CreativeDO>() : creativeDTOs); map.put("total", (null == total) ? 0 : total); return map; }