再谈 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 的撰写对比着之前的例子,几乎很难出错。这样的测试例,你三五分钟写出一个来是轻而易举的事情。

原文发布于微信公众号 - 程序人生(programmer_life)

原文发表时间:2016-03-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互扯程序

手把手教你调用百度人脸识别API

现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

1.3K9
来自专栏.net

干货,比较全面的c#.net公共帮助类(Common.Utility)

       网上有各式各样的帮助类,公共类,但是比较零碎,经常有人再群里或者各种社交账号上问我有没有这个helper, 那个helper,于是萌生了收集全部h...

2038
来自专栏肖洒的博客

Java调用Python的错误

因为这篇Java调用Python 之前试过用Java调用Python,到真正用的时候才发现是一个乌龙。

1092
来自专栏ASP.NETCore

Asp.Net Core 通过中间件防止图片盗链

  要实现防盗链,我们就必须先理解盗链的实现原理,提到防盗链的实现原理就不得不从HTTP协议说起,在HTTP协议中,有一个表头字段叫referer,采用URL的...

643
来自专栏信安之路

从0开始编写信息收集器

我们都知道在信息收集是整个渗透测试中无可或缺的一环,那我们老是需要一类一类信息去查询非常耗费时间,(人生苦短,我用 python)那这时我就想做一个信息收集器,...

740
来自专栏FreeBuf

滥用Edge浏览器的“恶意站点警告”特性,实现地址栏欺骗

前言 在过去的几个月里,我们看到使用这种以技术支撑的骗术日益增多,用户的浏览器会被辣眼睛的红屏以及类似”你的电脑可能存在风险”的提示消息”锁定”。当然,这种情形...

1949
来自专栏deed博客

day01笔记

1565
来自专栏FreeBuf

微信支付SDK 0元购Hack思路分享

* 本文作者:zjie2O71,本文属FreeBuf原创奖励计划,未经许可禁止转载

1023
来自专栏企鹅号快讯

专为渗透测试人员设计的 Python 工具大合集

如果你对漏洞挖掘、逆向工程分析或渗透测试感兴趣的话,我第一个要推荐给你的就是Python编程语言。Python不仅语法简单上手容易,而且它还有大量功能强大的库和...

2028
来自专栏腾讯Bugly的专栏

移动App入侵与逆向破解技术-iOS篇

如果您有耐心看完这篇文章,您将懂得如何着手进行app的分析、追踪、注入等实用的破解技术,另外,通过“入侵”,将帮助您理解如何规避常见的安全漏洞,文章大纲: 简单...

1K6

扫码关注云+社区