大家好久不见了。今天的文章将介绍jinja2模板在接口测试数据上的应用。
这几个月我在想,进阶系列要怎么写。
毕竟很多情况下,我觉得写技术文章和做培训一样,都会有两个结果:
1.是需要这些知识的人看不懂。
2.是看得懂的人不需要这些知识。
而我的进阶系列是希望写给即将跨入测试开发这条路的人和刚进入这条路还没有形成成自己的知识体系的人。当初我刚开始转型的时候就很希望有这种资料来引导一下我。。。如果说之前的基础系列是给想转型测试开发的人打打基础,那进阶系列就是真正走出这一步时最需要的东西。按照我的观点,真正走上测试开发之路的标志应该是你能够:
1.独立学习一个新工具或库。
2.独立完成一个测试框架或工具。
3.独立集成一个持续集成测试系统。
如果连一个框架也写不出来,就别说测试开发了,写框架之类的是很简单的,难的是写得好。
总之,文章限于篇幅,只能说是写给有足够自学能力的人看的。之后可能会做一些live或视频之类来提供给更希望在有人引导下学习的人。
不管怎么样,进阶我想从接口测试开始写起。接口测试的需求还是挺大的。而且你能看到各大测试论坛或讨论区里无数的人在写自己的接口测试框架。但是,那些开源出来的框架大都有很大的问题,比如最严重的问题就是:过于复杂。你去下载一个的话,你会发现你压根搞不懂他们写这么多代码是要做什么。而且你会发现很多人喜欢把别人写过的东西用自己的方式再写一遍。当然他在写的时候提高了一定的开发能力,但对于读者来说,那就是灾难。
我写代码喜欢简单,我对于测试开发的第一要求就是简单。
今天讨论的问题,我用了15行代码来实现一个解决方案。
这个问题就是,怎样保存接口测试的测试数据。
很多测试框架选择把数据放在:Excel里,CSV里,YAML里,源代码里。
但这些都不是我想要的,Excel太笨重,CSV很土,YAML里太难调,源代码里太难读。
归根结底,要么不灵活,要么很难读。
接口测试的数据问题
这个问题也算是由来已久了。
现在很多各种各样的方法来解决:
写Excel里,CSV里,YAML里,源代码里,XML里,还有放在数据库里,放在各种各样的文件里。我想说的是,你在真正做一个工具时,这些都可以。关键是能应付你的项目需求。
这里我给出的是一个示例,也就是我怎样写一个测试框架的数据管理部分。当然,这个示例还是比较精致的。至少我再也不会选择把数据放到上面提过的那些东西里去。。。
那么我把数据放在哪里?json文件里。
先来看需求,假如有这么一个接口,
你要在HTTP请求的BODY要发送{"username":"user1","password":"password1"}这样一个json对象来表示你登录时使用的用户名密码。
那么直接把数据写在代码里的话,差不多就是这样:
import pytest
import requests
import json
def test_login():
url = "http://localhost:8000/login"
data = json.loads({"username":"user1","password":"passowrd1"})
r = requests.post(url=url, data=data)
assert r.status_code == 200
if __name__ == "__main__":
pytest.main()
接下来的问题是很常见的数据驱动问题,如果你要测5个用户的登录。
他们的用户名密码依次是user1/password1,user2/password2,...user5/password5.
那么直接把数据写在代码里的话,大概就是这样:
注意这里我把http response code也写在测试数据里了,虽然他是预期结果。但预期结果也是数据的一部分。这个test方法里其实可以测一些返回值不是200的数据。
import pytest
import requests
import json
testdata = [
("user1", "password1", 200),
("user2", "password2", 200),
("user3", "password3", 200),
("user4", "password4", 200),
("user5", "password5", 200)
]
@pytest.mark.parametrize("user,password,expected_response_code", testdata)
def test_login(user,password,expected_response_code):
url = "http://localhost:8000/login"
data = json.loads({"username":"{}".format(user),"password":"{}".format(password)})
r = requests.post(url=url, data=data)
assert r.status_code == expected_response_code
if __name__ == "__main__":
pytest.main()
到这里为止,data = xxxx 这一行已经有点复杂了。
然后,一个真实请求的json里可能包含不止这么两个参数。
可以想象如果全写在代码里,你的测试用例文件会变得多么难读。。。
举个例子,github api里create repo可以包含14个参数,他给出的例子里用了7个参数,
长这样:
{
"name": "Hello-World",
"description": "This is your first repository",
"homepage": "https://github.com",
"private": false,
"has_issues": true,
"has_projects": true,
"has_wiki": true
}
如果这14个参数里有些不变,有些你要测也就是会变,你可以试一下直接写在代码里会形成怎样的一个测试用例文件。。接口的描述文档在这里Repositories | GitHub Developer Guide
可能到这里,大家都会想把数据驱动更进一步,把数据或者至少是一部分数据放到代码外吧。这就是那些excel,xml,yaml,各种数据源出现的原因了。这些都能用,也都能解决一定的问题。但是,就是不灵活。
然后这是一个网友遇到的实际问题:他测的接口要求当参数A的值为true时,才向服务器发送参数B,C和D,其他情况向服务器发送参数E和F。
我可以说上述各种数据源都做不到这种功能。你要么写两份数据,要么只能在代码里做分支。但这些,都在增加测试的复杂度,而我不想要复杂。
ok,现在我要解决这个问题。
测试数据中引入模板概念
模板其实对于python的web开发者来说是很熟悉的东西。Django,flask,各种web开发框架里,前端部分都在用模板解决问题。
通俗的说,模板就是在html里插入了一些特殊符号,让HTML里可以拿到服务器后台传上来的变量,更进一步可以根据这些变量的值来显示不同内容。
比如后台发现你是免费用户,就给你显示一堆广告,发现你是付费用户,这些广告就不显示了。
那么,我就想,对于测试数据,可不可以也这么搞?
显然是可以的
数据我划分成可变数据和不变数据两个部分。
比如一个HTTP请求要发送{"username":"xxxx","password":"xxxx"}
这个数据里除了xxxx以外,全是不变部分。至少不会经常不停的变。那么不变部分直接写死在json文件里。
而可变部分在json文件里用模板语法代替,这个json文件看上会是这样的:
{"username":{{user.username}},"password":{{user.password}}}
这里{{user.username}}和{{user.password}}就是可变部分。不直接写死他们的值。而是,用模板的变量代替。
这个变量就是{{user}},这个变量有两个属性,一个username一个password,之后我们再在测试用例里把这个变量传递给模板,就可以渲染出真正的测试数据。也就是说,这个可变部分,真正写,我写在测试用例的代码里面。
源代码说好了一共只有15行:
读取和渲染模板的类
from jinja2 import Environment, FileSystemLoader
import json
class JsonTemplateReader():
def __init__(self):
self.env = Environment(loader=FileSystemLoader('.'))
def get_data(self, test_name, **kwargs):
template = self.env.get_template(test_name + ".json")
return json.loads(template.render(**kwargs))
给pytest用的fixture
import pytest
from core.data_reader import JsonTemplateReader
@pytest.fixture(scope="function")
def json_template(request):
def _to_read_the_template_by_test_name(**kwargs):
return JsonTemplateReader().get_data(request.function.__name__, **kwargs)
return _to_read_the_template_by_test_name
在这里我的设计思想是这样的,针对单个接口的测试(我把接口测试分为针对单个接口的测试和针对用户场景的端到端测试):
一个测试方法,对应一个json文件。
比如"test_user_login"这个方法,对应"test_user_login.json"这个文件,根据测试方法名去识别对应的json文件名。这里的json模板具有jinja2模板的所有功能,也就是说,可以根据后台传入的数据,来做if判断。可以实现用同一份数据,在某个参数值为TRUE和FALSE时传不同的数据给服务器。这里的所谓后台,其实就是指我们在测试用例里传给json模板的数据值。这部分数据也是我前面说的可变数据。
好,关于if else的具体例子我就略了,相信读者理解了模板和相关的库后可以很容易写出来。我会在后期慢慢把例子加在我这个github的samples目录里。现在已经加了最基本的测试数据模板渲染例子。
另外我的库里使用hug来编写测试用的rest api,代码极其简单,强烈推荐这个库给大家。
比如这样,就实现了一个get的api和一个post的api,每个接口3行代码,毕竟搞python就是要搞一些简单的东西来用嘛。
import hug
@hug.get()
def hello():
return "hello"
@hug.post()
def post_a_user(user):
return {"your_input":user+"X"}
最后: 为了回馈铁杆粉丝们,我给大家整理了完整的软件测试视频学习教程,朋友们如果需要可以自行免费领取 【保证100%免费】
全套资料获取方式:点击下方小卡片自行领取即可