GraphQL 测试实践
我们很熟悉以REST实现的API,可以用任何能够发出http 请求的库或者工具来测试REST API。去年随着GraphQL在全球风靡,它也出现在了最近两期的ThoughtWorks技术雷达中,当我们面对新的GraphQL APi时,QA应如何应对? 知彼知己,方能百战百胜,下面让我们首先来看看什么是GraphQL,它和传统的REST API又有什么不同?
什么是GraphQL ?
Graph + Query Language =图表化(可视化)查询语言
是一种描述客户端如何向服务端请求数据的API语法,类似于 RESTful API 规范。由Facebook开发,用于替换RESTful API。服务端可以用任何的语言实现。
由于这篇不是GraphQL科普文,我们大概介绍下几个重要概念,给后面测试做铺垫。
Schema:Schema由服务端来定义,用于定义API接口,并依靠Schema来生成文档以及对客户端请求进行校验。Schema只是一个概念,它是由各种数据类型及其字段组成,而每个类型的每个字段都有相应的函数来返回数据,且Schema里的字段可以聚合其他Schema,我们可以将Schema理解为多个Query组成的一张表。

Type: 类型是用来描述 API 外观的自定义对象。对于数据模型的抽象是通过Type来描述的,每一个Type有若干Field组成,每个Field又分别指向某个Type。
**Query: **有了Schema 有了Type,相对于传统的API的CRUD,我们接下来需要怎么做,Query就出现。Query 的查询语法和格式受 Schema 约束,而 Query,Mutation,Subscription 是 Query 的三种类型,分别对应不同的业务场景。
总结下:
- 前端自己定义返回的数据及结构,降低前后端沟通成本
- 无需编写接口文档(GraphQL会根据schema自动生成API文档)
- Schema拼接,可以组合和连接多个GraphQL API,合并为一个,减少请求次数
- GraphQL是强类型的,通过它,可以在执行之前验证 GraphQL 类型系统中的查询, 它帮助我们构建更强大的 API。
如何简单快速的手动测试GraphQL?
我们先来看看 GraphQL与RESTful的区别

上图我们其实可以发现一些蛛丝马迹,再来个更直观的
以https://www.squarefoot.com.hk/en/

传统的REST请求
无论GET还是POST 都是以REST Server Host URL 加请求的Path 再加Query 或者Body 来发送请求
而对于GraphQL

则是GraphQL Server Host Url 加Query 或者Body 来发送请求。
所以第一个不同就是GraphQL 缺失了path。但从请求URL上我们无法辨别GraphQL到底干了什么,我需要进一步的观察请求的body
REST api 请求POST带的请求的数据。同样GraphQL它也是发送的POST请求,也是带的数据。
但是不同点就在于内容。我们来看看GraphQL Body

{
"operationName":"FeaturedProperty",
"variables":{
},
"query":"query FeaturedProperty { featuredListing(channel: \"sale\", pageSize: 4) { items { id title subtitle description propertyType prices { min max monthlyPayment currency symbol label type __typename } address { lat lng formattedAddress __typename } propertyType cover { url urlTemplate __typename } organisations { id name color logo { url urlTemplate __typename } type __typename } listers { id type name __typename } tier channels attributes { sizeUnit pricePSF minimumPricePerSizeUnit maximumPricePerSizeUnit builtUp landArea bedroom bathroom __typename } multilanguagePlace { enGB { level1 level2 level3 __typename } idID { level1 level2 level3 __typename } zhHK { level1 level2 level3 __typename } zhCN { level1 level2 level3 __typename } __typename } __typename } __typename }}"
}
GraphQL 请求的Body里面包含的是我们在GraphQL server中 定义的Query 和Schema中的字段。而REST POST请求则直接包含的是我们要发送的数据。所以GraphQL 那里客户端 可以拿自己想拿的数据,但REST api 只能请求 server 定义的api。
回到正题,刚才主要还是停留在REST api与GraphQL一些基本的区别,那么怎么手动测试。
对于GraphQL 我们需要借助一些第三方库来测试,例如:graphiql与graphql-playground 都能帮我们很好进行基本的验证。

上面无论是graphiql还是graphql-playground 我们都能够利用它发送具体请求,来查看结果是否正确。但是当查询的api 返回几万条,我们需要验证某一些数据与逻辑的正确性时,graphiql与graphql-playground 就显得力不从心。
怎么办?我们需要一种能发出GraphQL 请求并且有相应断言框架的工具。
我们可以利用代码来实现,但对于项目中所有角色,尤其是一些没有代码经验的人,让他们去看代码实现是非常痛苦的,自动化测试本质是能够帮我们快速回归,验证完成功能是否受到影响,并且你的测试代码或工具能够让每个角色轻松理解并能够快速简单使用,而不是秀你的代码多么炫酷用了多么复杂的算法,一切以实用为本。
基于以上事实,我选择所有角色都用过Postman工具来实现GraphQL 手动测试。
首先怎么将GraphQL于Postman结合?
如果你的前端不是服务器渲染的话,我们可以从chrom dev tools 查看到客户端发生的请求。
如果是服务器渲染的话,我们可以利用上面说过的graphiql与graphql-playground去手动执行需要测试的请求,然后同样在chrome dev tools 查看具体的请求。
我们知道具体请求的Query内容,有的内容很长,难道让我们一个一个字全打到Postman上?

这里有一个小技巧,鼠标右键在chrome dev tools 具体请求上点击,然后选择copy 选择copy as curl 这样就能简单复杂这条请求。
步骤二,打开Postman 创建完我们的work space后,选择import 在将复制的请求 Paste Raw Text

导入后 就会在我们创建的work space里面创建这条测试

步骤三 利用Postman test script 来编写对应检查点

光有了这种可视化的脚本运行是远远不够的,我们需要将我们编写的测试脚本与CI集成,并入我们整个开发流程中才能算完美,Postman提供了newman 这个第三库方便我们能够将Postman中export出的脚本,在命令行中快速执行,并结合Docker与Linux Shell使我们能够更方便的与任何CI集成。
#!/usr/bin/env bash
set -ex
docker run --rm -v $(pwd):/etc/postman \
-w /etc/postman \
-t postman/newman_ubuntu1404 \
run "./postman/test.postman_collection.json" \
--environment="./postman/test.postman_environment.json" \
--timeout-request 20000
最新的版本的Postman 已经支持了对于GraphQL 测试,如果你还用的旧版本的Postman,请参考以上步骤,如果你已经下载了最新版本的Postman, potman官网已经给了说明文档,https://learning.getpostman.com/docs/postman/sending_api_requests/GraphQL/ 请参考链接。

利用测试脚本实现GraphQL自动化api测试
上面主要介绍如何手动测试GraphQL,当然我们也可以利用代码来实现GraphQL 测试。
传统上我们测试RESTful时,大部分人可能选择的mocha chai supertest 这个库作为测试框架 来编写API测试,通过上面的文章,我们了解到GraphQL 请求的底层依然还是http request,所以我们依然能够采用这套非常成熟的架构,但是我们今天要将mocha 替换成AVA,因为它支持并发,能够帮我们进一步的提升测试效率。
安装依赖库
npm init -y
npm i --save-dev mocha chai ava
测试代码如下
import test from 'ava';
import { expect,should } from "chai"
import supertest from 'supertest';
const server = supertest.agent("http://localhost:3000/GraphQL");
test('test grahphql api',async (t)=>{
const res = await server
.post("/")
.send(
{"operationName":null,"variables":{},"query":"{\n student(id: \"1\") {\n id\n name\n age\n sex\n }\n}\n"}
)
.set('Content-Type', "application/json")
.expect(200)
.end();
res.body.user.should.have.property('name')
});
上面的代码看起来也挺简单,但是这不是我们最优方案,我们可以看到 request 的query 由于要遵循GraphQL 规范,并不是我们常见的json对象,我们需要寻找一种能够代替supertest 发送规范化GraphQL Query 和 Mutation 请求的工具,ApolloClient就是一个不错的选择。
安装对应依赖库
npm --save-dev install apollo-boost GraphQL
测试代码如下
import test from 'ava';
import { gql } from "apollo-boost";
import ApolloClient from "apollo-boost/lib/index";
import fetch from 'node-fetch';
const client = new ApolloClient({
uri: "http://localhost:8080/GraphQL",
fetch: fetch
});
const GET_STUDENT = gql`
query($id: String!) {
student(id:$id){
id
name
age
sex
}
}
`;
test('first scenario',async (t) => {
const res =await client
.query({
query: GET_STUDENT,
variables: {
id: '1',
},
});
console.log("+++++++",res.data);
t.is(res.data.student.id, '1')
});
运行通过
qilei/study/ava-api-testing
▶ npx ava -v
+++++++ { student:
{ id: '1', name: 'Tom', age: 25, sex: true, __typename: 'Student' } }
✔ grahpql api testing (115ms)
1 test passed
上面的ApolloClient 与ava.js 测试架构,可以复用之前开发代码编写过query或mutation,更适合dev快速编写api集成测试。
综上,对于这些新的事物,我们要看清它的本质,才能快速理解它的架构及原理,从而进一步找到适配的测试框架或工具,来帮助我们更好的提升项目质量。最后还要再次强调,自动化测试框架的设计与选型,应该以人为本,适合项目所有成员的工具,才能够提升整体团队的协作效率。
更多精彩洞见,请关注微信公众号:ThoughtWorks洞见