# 强行为新项目写接口测试是一种什么样的体验


继续上次的话题，为新项目写 ui 自动化测试是一件非常有挑战的事情，写接口测试会不会容易一点呢？这次我就尝试了一下。

现阶段我们的管理端接口其实不多，就 8 个左右，所以从工作量上评估其实还可以。

### 测试策略

讲策略之前我们先看一下项目的简单业务属性。该项目的管理后台其实就是稍微复杂一点的增删改查。增加一条记录，编辑记录，各种组合条件查询记录，删除功能暂时没有，后面可能会跟进。

我的测试策略也很简单，首先搭建 1 个单独的测试环境，防止跟其他测试形式冲突，然后把最大的精力放在数据的准备和清理上。

- 准备数据：为了测试查询，比较好的方式是每次都先清空数据库，然后动态创建一些固定的数据，我指的固定是比如固定 100 条，每条的排序规则，各个字段都是确定的。
- 数据清理：完成测试后清空数据库，比如搜索测试完成之后清空数据库去测试创建和编辑，这样创建的时候就没有存量数据造成的断言干扰，就可以实现每次创建之前数据都是 0 条，创建成功之后变成 1，这样断言就相对容易，而且用例能够以随机的顺序运行，减少了依赖；

### 代码实现

这里最有挑战的是数据准备和清理的代码实现，我需要准备下面的数据

- 通过接口创建一些数据到 db，之所以用接口创建是因为一些联动的操作直接写 db 的话无法触发。我们的接口是 http 的，用 python+requests 就可以了，稍微麻烦的点是构造的数据需要通过接口的有效性校验，比如时间区间之类的，因为我们的服务会跑在多个不同的国家和地区，timezone 也是需要关注的
- 把创建成功的数据 id 以及一些需要用到的字段存到 redis 里。我习惯于用 redis 的 set，因为天生去重，并且可以使用`srandmember`来随机返回几条数据，在做查询校验的时候非常方便
- 查询一些关联表的信息或者需要用到的数据，保存在 redis 里。这里我用的是 redis 的 string 类型，需要保存的信息直接序列化成 json 字符串，非常的方便；
- 直接写 sql 做数据库的清理，直接调用 es 的 resetful api 做索引的清理。我们的查询使用的是 es，所以清理数据的时候除了用 sql 去 delete 之外，es 的 index 也是要清理的
- 使用配置文件来进行多环境多地区的配置，比如 mysql 每个地区的配置都不一样，同样的地区不同的业务数据也在不同的库里，把配置弄起来还是很有必要的

最终实现的效果是运行`TEST_ENV=test REGION=cn python data_builder.py`这条命令之后就可以在对应的环境创建初始化数据了。代码比较啰嗦我就不粘贴了。

### 用例编写

用例编写无非是增删改查。

新增记录

- 测试各种边界条件，比如一些字段本来是必填的，就是任性不填。本来要求 string 类型结果传 number 类型等待
- 测试正常的情况，起码要把正常流程跑通

下面是一个例子

```python
def test_ads_target_keyword_list_should_be_mandatory(valid_data_set):
	del valid_data_set['target_keyword_list']
	res = common_helpers.post_with_cookie("/create", valid_data_set)
	common_assertion(res)
	assert res['code'] != SUCCESS
	assert 'required' in res['msg']
```

这里可以分享 2 点有意思的地方

- 返回值的错误码尽管可以枚举，但是逐一判断还是比较麻烦的，这里我就直接断言返回码不等于 SUCCESS，SUCCESS 其实可以看成是一个宏，所有用例里都统一
- 错误信息可以不需要精确判断，判断几个关键字是否存在就好了，比如上面例子里的 required

编辑记录

- 从之前生成的记录里随机挑 1 条进行更新，因为我把所有初始化的记录 id 都存在了 redis 的 set 里，这一步其实就非常的自然了
- 测试各种边界情况，除了字段的合法性之外还要测试一些编辑特有的用例，比如只能编辑自己的记录
- 测试正常编辑成功的情况

搜索及查询

- 因为数据是提前生成好的，所以我们可以构造各种条件来进行断言，比如我就构造了 10 条 active 状态的记录，10 条 inactive 的记录，按照状态来搜索时我可以直接断言，active 就是 10 条，多一点少一点就是 bug
- 构造多维度的搜索条件，这样覆盖面就更广一点，比如按 A 字段搜索，按 B 字段搜索，按 A 和 B 组合在一起搜索等

比如这个用例

```python
def test_search_with_no_conditions():
	page_size = 20
	data = {
		'filter': {},
		'page_num': 1,
		'page_size': page_size,
	}
	res = common_helpers.post_with_cookie(f"/action/search/list", data)
	assert res['code'] == SUCCESS
	assert len(res['data']['data']) == page_size
```

这里我没有传入任何条件，只判断了当前返回的数据条数必须等于 page size，因为我之前创建了足够多的数据，所以这个用例是非常安全和稳定的

### 总结

- 最好有单独的测试环境
- 用配置文件来保存各个环境的配置，有配置中心更好，这样就不用改代码
- 在运行用例时通过环境变量来指定环境和地域，如果你的服务需要跑在多个国家和地区的话
- 每次都创建自己的测试数据集，这样断言起来会更加的容易
- 创建数据的脚本也可以用来进行 ui 自动化测试

