zoukankan      html  css  js  c++  java
  • 谈 API 的撰写

    在做一个系统时,有一些子系统几乎是必备的:配置管理,CLI,以及测试框架。

    配置管理

    我们先说配置管理。一个系统的灵活度,和它的配置管理是离不开的。系统中存在的大量的预置的属性(下文简称 property),需要有一个公共的地方来放置。这里我不说「常量」,而是说「预置的属性」,是因为这属性可能需要在运行时发生改变,而常量的范畴会让人有所误解。

    最简单的配置管理就是把所有的 property 放在一个配置文件中,在系统启动的时候读入。配置文件的类型有很多选择:ini,json,yaml,toml 等。这些类型各有优劣,选择的时候注意配置文件最好能够支持注释,便于维护。从这个角度看,json 不是个太好的选择。toml 可能大家用得不多,它是 github 创始人 Tom 定义的一种格式,类似于 ini 但灵活不少,感兴趣的可以在 github 里搜索 toml。

    我们知道,一个项目会有多种运行时:development,staging,production,test。不同的运行时加载的配置文件可能不同。所以配置管理需要考虑这一点,让配置文件可以重载(override)。最常见的重载策略是系统提供一个公共的配置文件:default,然后各种运行时相关的配置文件继承并局部重载这个配置。在系统启动的时候,二者合并。

    有些时候,我们需要在系统运行的时候改写配置。由于配置一般在系统初始化的时候就被读入内存,所以单纯改写配置文件无法即时生效,这时,你需要像管理缓存一样去管理和配置相关的数据,将其封装在一个容器里:当配置被修改时,调用这个容器的 invalidate 方法 —— 这样,下次访问任意一个配置项时,会重新读入配置,并缓存起来。

    对于分布式的项目,配置应该集中存储在诸如 redis 这样的系统,以方便统一处理(orchestrate)。

    CLI

    写 CLI 并非难事,但一个 CLI 子系统的难点是:

    • CLI 的发现和自注册。你的 framework 的用户只要遵循某种 convention 撰写 CLI,这些 CLI 就会被自动集成到系统里。

    • CLI 的撰写者能够轻松地获取到系统的信息,也就是说,系统有自省(introspection)的能力。

    前者的实现我们在前面的篇章里(谈谈编译和运行)讲路由是如何注册的已经提到,这里就不赘述。后者非常重要,在展开讨论之前,我们先考虑一个问题:做一个系统的过程中,我们希望这个系统的 CLI 解决什么问题?

    首先,CLI 显然不是给用户用的,是给程序员用的,所以,CLI 提供一些简化程序员工作的脚本。那么,作为一个 API 系统,程序员都需要哪些 CLI 呢?我们看一些例子:

    • 创建某些 skeleton - rails / django 都有新建项目,新建 model / controller 等的 CLI

    • 获取系统的信息。比如:不用看代码就能很快知道系统里都有哪些 route,哪些 model 等。

    • 生成某些信息或者模拟某些行为。当你调试你的系统时,每次生成某种状态很烦人,比如说登录,可以通过 CLI 一键完成。

    • ...

    这些例子大部分都需要系统的自省的能力,比如说下面这个 CLI:

    在这个 CLI 的执行函数里,我们使用了这些系统信息:

    • 系统中所有 app 的名字。

    • 某个 app 的 instance。

    • app 的配置信息。

    • app 启用的 middleware。

    • app 的所有的路由。

    • ...

    如果我们无法在系统的非运行时获取这些信息,那么,CLI 的威力会大打折扣。这也印证了我之前的文章 里所述的将「编译时」和「运行时」分开的重要性。很多框架,如 express.js,由于无法很清晰地将二者区分开,以至于你想在非运行的时刻获取 route / middleware 的信息,非常困难。

    测试框架

    API 的测试是相当无趣的(几乎所有的测试例撰写起来都相当无趣),但是测试的重要性是不容置疑的,尤其对于一个不断重构的代码。如果说别的系统的测试只能在局部寻找规律而进行优化,API 的测试,尤其是 functional testing 是可以全局考虑的。

    比如你有一个 API 是 PUT /feature/:id,要测试这个 API 是否工作正常,你大概会考虑这些测试例:

    • PUT 正确的数据到一个错误的 id,测试是否会出错;

    • PUT 错误的 etag,测试 concurrent udpate 是否工作;

    • PUT 空数据,测试 validator 是否正常工作;

    • PUT 错误的数据,测试 validator 是否正常工作;

    • PUT 正常的数据,测试基本功能是否工作。

    这些测试例有这些共同之处:

    • 需要运行一个 temporary server

    • 需要发送请求到 temporary server 上

    • 需要检测 status code,以及 response header / body 来确认是否出现期待的结果

    如果每个测试都写一个测试例,虽然每个的代码量并不太大,但测试一个 API 就需要 5 个测试,API 的规模一上,代码量就大了,添加和维护都很麻烦。

    我们可以定义一种针对于此的测试语言来描述测试的 fixture:

    这个定义非常简单,相信大家都能看明白:

    • 测试的描述(用于 test report)

    • 测试所用的 url

    • 期待的结果,包括 status code,headers 和 body

    这里面,我们用了一种很简单的方式区分 field name 和函数。比如 body 下面的 #length,它的结果不是body['length'] 而是 length(body),前者虽然对 array 有效(javascript),但对 object 无效。

    要运行这样的 fixture,并不需要撰写太多的代码(假设我们是用 ava 作为测试工具):

    这里面,runAssertion 发送 request,并对比 fixture 里面的数据和 response,来确定一个 test case pass 或者 fail。

    这样下来,我们成功地把繁琐的 test case 的撰写转化成一个 parser 和一系列 fixture 的撰写。parser 的撰写是一次性的,以后改动很少(但会添加新的功能,比如新的函数 - 如上的 #xxx),而 fixture 的撰写对比着之前的例子,几乎很难出错。这样的测试例,你三五分钟写出一个来是轻而易举的事情。

  • 相关阅读:
    maven工程目录结构&常用命令
    slf4j 与 log4j2 实战讲解与日志分割
    跟着大神学zookeeper分布式锁实现-----来自Ruthless
    redis分布式锁Redisson扩展
    Redis分布式锁---完美实现
    redis异常Redis:java.util.NoSuchElementException: Unable to validate object at
    boot中 Quartz注入spring管理类失败
    转载一篇必须超级好的JVM配置实战
    uoj#422. 【集训队作业2018】小Z的礼物(MIn-Max容斥+插头dp)
    uoj#420. 【集训队作业2018】矩形(组合数学)
  • 原文地址:https://www.cnblogs.com/annie00/p/5773159.html
Copyright © 2011-2022 走看看