回顾

上一节,我们说到了关于整个用例执行的生命周期。我后面细想了一下,如果一开始就要完全兼容代码生成的话,写起来会很费时间。为了给大家一些好的体验,还是得一口一口地吃,不能一下吃胖。

所以我们就先完成无码模式吧~

粗略地设计几个核心表

我们的用例后续如果要方便管理,肯定是需要做一些分类权限的控制,所以我们先设计几个核心的表:

  • 项目表
    用例分步在各个项目,类似于目录的概念,不同的用例位于不同的项目之中,方便用例进行分类。

  • 项目角色表
    每个项目都是需要有自己的成员,对于成员们来说,他们因为身份的不同,身上担负的职责也不一样,我们给出几个简单的身份如:
    组长和组员,其中组长可以对组员进行相关权限的变化。

  • 测试用例表
    存放了各个用例的信息,是测试平台的一个最小的执行单元。

项目表 models/project.py

  1. from app.models import db
  2. from datetime import datetime
  3. class Project(db.Model):
  4. id = db.Column(db.INT, primary_key=True)
  5. name = db.Column(db.String(16), unique=True, index=True)
  6. owner = db.Column(db.INT)
  7. created_at = db.Column(db.DATETIME, nullable=False)
  8. updated_at = db.Column(db.DATETIME, nullable=False)
  9. deleted_at = db.Column(db.DATETIME)
  10. create_user = db.Column(db.INT, nullable=True)
  11. update_user = db.Column(db.INT, nullable=True)
  12. private = db.Column(db.BOOLEAN, default=False)
  13. def __init__(self, name, owner, create_user, private=False):
  14. self.name = name
  15. self.owner = owner
  16. self.private = private
  17. self.created_at = datetime.now()
  18. self.updated_at = datetime.now()
  19. self.create_user = create_user
  20. self.update_user = create_user
  21. self.deleted_at = None

大概分为以下几个字段,除了创建/更新时间/用户以外,还有以下字段:

owner(组长)
name(项目名)
private(是否私有)

项目角色表 models/project_role.py

  1. from app.models import db
  2. from datetime import datetime
  3. class ProjectRole(db.Model):
  4. id = db.Column(db.INT, primary_key=True)
  5. project_id = db.Column(db.INT, index=True)
  6. project_role = db.Column(db.INT, index=True)
  7. created_at = db.Column(db.DATETIME, nullable=False)
  8. updated_at = db.Column(db.DATETIME, nullable=False)
  9. deleted_at = db.Column(db.DATETIME)
  10. create_user = db.Column(db.INT, nullable=True)
  11. update_user = db.Column(db.INT, nullable=True)
  12. def __init__(self, project_id, project_role, create_user):
  13. self.project_id = project_id
  14. self.project_role = project_role
  15. self.created_at = datetime.now()
  16. self.updated_at = datetime.now()
  17. self.create_user = create_user
  18. self.update_user = create_user
  19. self.deleted_at = None

与上个表单类似,project_id是项目id,project_role是项目角色。

咱们规定:

1: 组长 2: 组员

测试用例表 models/test_case.py

  1. from app.models import db
  2. from datetime import datetime
  3. class TestCase(db.Model):
  4. id = db.Column(db.INT, primary_key=True)
  5. name = db.Column(db.String(32), unique=True, index=True)
  6. request_type = db.Column(db.INT, default=1, comment="请求类型 1: http 2: grpc 3: dubbo")
  7. url = db.Column(db.TEXT, nullable=False, comment="请求url")
  8. request_method = db.Column(db.String(12), nullable=True, comment="请求方式, 如果非http可为空")
  9. request_header = db.Column(db.TEXT, comment="请求头,可为空")
  10. project_id = db.Column(db.INT, comment="所属项目")
  11. tag = db.Column(db.String(64), comment="用例标签")
  12. status = db.Column(db.INT, comment="用例状态: 1: 待完成 2: 暂时关闭 3: 正常运作")
  13. expected = db.Column(db.TEXT, comment="预期结果, 支持el表达式", nullable=False)
  14. created_at = db.Column(db.DATETIME, nullable=False)
  15. updated_at = db.Column(db.DATETIME, nullable=False)
  16. deleted_at = db.Column(db.DATETIME)
  17. create_user = db.Column(db.INT, nullable=False)
  18. update_user = db.Column(db.INT, nullable=False)
  19. def __init__(self, name, request_type, url, project_id, tag, status, expected, create_user, request_header=None,
  20. request_method=None):
  21. self.name = name
  22. self.request_type = request_type
  23. self.url = url
  24. self.project_id = project_id
  25. self.tag = tag
  26. self.status = status
  27. self.expected = expected
  28. self.create_user = create_user
  29. self.update_user = create_user
  30. self.request_header = request_header
  31. self.request_method = request_method
  32. self.created_at = datetime.now()
  33. self.updated_at = datetime.now()

基本上内容都在comment注释里面了,大家看着就行,也不多讲解了。肯定现在还有一些特性没有考虑到的,后序可以加字段解决。

编写检查用户权限的装饰器

我们在上面三张表里面定义好了create_userupdate_user,其实这2个字段接受的都是咱们的用户id,而用户id又是可以通过token进行解析的。

测试平台系列(18) 项目用例表设计和权限改造 - 图1

还记得之前编写过的解析token的方法吗?我们现在就来编写一个装饰器: 自动判断用户token是否有效以及获取用户信息。

测试平台系列(18) 项目用例表设计和权限改造 - 图2

原理很简单,就是通过headers中的token字段获取token,经过解析后拿到用户信息,这里更加复杂一点,对于那种需要有权限控制的地方,我们做了一层判断,如果权限在配置范围以下,那么普通成员就不能调用这个接口。

完整代码:

  1. from functools import wraps
  2. from flask import request, jsonify
  3. from app import pity
  4. from app.middleware.Jwt import UserToken
  5. FORBIDDEN = "对不起, 你没有足够的权限"
  6. class SingletonDecorator:
  7. def __init__(self, cls):
  8. self.cls = cls
  9. self.instance = None
  10. def __call__(self, *args, **kwargs):
  11. if self.instance is None:
  12. self.instance = self.cls(*args, **kwargs)
  13. return self.instance
  14. def permission(role=pity.config.get("GUEST")):
  15. def login_required(func):
  16. @wraps(func)
  17. def wrapper(*args, **kwargs):
  18. try:
  19. headers = request.headers
  20. token = headers.get('token')
  21. if token is None:
  22. return jsonify(dict(code=401, msg="用户信息认证失败, 请检查"))
  23. user_info = UserToken.parse_token(token)
  24. # 这里把user信息写入kwargs
  25. kwargs["user_info"] = user_info
  26. except Exception as e:
  27. return jsonify(dict(code=401, msg=str(e)))
  28. # 判断用户权限是否足够, 如果不足够则直接返回,不继续
  29. if user_info.get("role", 0) < role:
  30. return jsonify(dict(code=400, msg=FORBIDDEN))
  31. return func(*args, **kwargs)
  32. return wrapper
  33. return login_required

kwargs["user_info"] = user_info

重点注意一下这句话。

为之前咱们编写的http/request接口加入权限

测试平台系列(18) 项目用例表设计和权限改造 - 图3

由于我们这个接口暂时支持登录用户使用,所以权限默认就行了,也就是说用户role为0(普通用户)即可!

测试平台系列(18) 项目用例表设计和权限改造 - 图4

这里需要注意的是,我们的http_request必须带有user_info参数,否则会报错,因为我们后续可能会用到用户信息。

改造前端代码

因为我们之前前端在请求接口的时候,是不会带上用户token的,所以我们需要改造前端请求方法:

  1. 在发送http请求的时候带上token数据
  2. 校验返回code,如果为401则自动注销用户,提示用户未登录!
  • 创建src/utils/auth.js
  1. import { message } from 'antd';
  2. export default {
  3. headers: (json = true) => {
  4. const token = localStorage.getItem('pityToken');
  5. const headers = { token };
  6. if (json) {
  7. headers['Content-Type'] = 'application/json';
  8. }
  9. return headers;
  10. },
  11. response: (res, info = false) => {
  12. if (res.code === 0) {
  13. if (info) {
  14. message.info(res.msg);
  15. }
  16. return true;
  17. }
  18. if (res.code === 401) {
  19. // 说明用户未认证
  20. message.info(res.msg);
  21. localStorage.setItem('pityToken', null);
  22. localStorage.setItem('pityUser', null);
  23. window.location.href = '/user/login';
  24. }
  25. message.error(res.msg);
  26. return false;
  27. },
  28. };

这里编写了获取token并组成headers的方法,也编写了针对token过期,权限不够的code码进行不同的处理。

  • 修改src/services/request.js

测试平台系列(18) 项目用例表设计和权限改造 - 图5

  • 修改请求结果:

测试平台系列(18) 项目用例表设计和权限改造 - 图6

  • 开始测试
    发现我们的错误信息并没有完全展示,是因为我们需要没有针对请求失败的情况进行校验,所以我们还需要调整后端接口:

测试平台系列(18) 项目用例表设计和权限改造 - 图7

判断里面的status是否为True,不为True的话返回110状态码

来个动态图测试一下吧:

测试平台系列(18) 项目用例表设计和权限改造 - 图8

接着测试一下权限

通过开发者工具的application可以看到:

测试平台系列(18) 项目用例表设计和权限改造 - 图9

接着我们调整一下权限为: 只有管理员可以请求接口

测试平台系列(18) 项目用例表设计和权限改造 - 图10

来个动图看看:

测试平台系列(18) 项目用例表设计和权限改造 - 图11


打完收工,最近清明可以稍微更新多一点😳

晚安,我的12个(包括我自己)观众们!

广告时间

预览地址: http://47.112.32.195/

后端代码地址: https://github.com/wuranxu/pity

前端代码地址: https://github.com/wuranxu/pityWeb

欢迎关注公众号测试开发入坑,还有加群一起讨论相关问题呀!如果群二维码过期了,可以加我个人微信: wuranxu 我拉你进群~

测试平台系列(18) 项目用例表设计和权限改造 - 图12

测试平台系列(18) 项目用例表设计和权限改造 - 图13