自动化工具 新潮测试平台选型之 flask_admin (原创)

新潮质量保障 for 新潮测试技术 · January 19, 2020 · Last by scu-zrb replied at May 21, 2020 · 894 hits

该文原创为新潮质量保障技术团队中的 “上进的中年软件测试从业者”,用于技术交流分享

思路清晰的人前途各有不同;不清晰只有一种后果,随波逐流。以我个人举例,最早12年就开始使用二次封装过的Eclipse作为公司的自动化,当时到了14年要被公司因为业务问题裁员的时候都不是知道那是大名鼎鼎的Eclipse,当时还是自动化组的负责人,写过很多python脚本。现在回头想想简直抠脚,同样颠沛流离了好几年。过往的经历后面会穿插介绍。
目前从大厂出来的,基本上都有测试平台的经历,也会提议所在的测试部门有自己的测试平台。慢慢的听多了,自己也会想尝试去搭建一套。由头有了,接下来就是干了,之前短暂的接触过flask_admin作为python web开发的框架。所以想也没想就用了这个框架。(下图是我们测试平台的首页,背景是借鉴别人的,主要是高大上)

当前这套框架的组成为flask_admin、Apscheduler、MongoEngine、Echart、ThreadPoolExecutor等。数据库包括mysql、mongo、redis(选用)等。其他相关联工具包括Jenkins、JIRA、Testlink等。

框架的选型

  • flask_admin: 选择flask_admin的原因,首先有个人原因,先入为主;其次对于web前端有与生俱来的恐惧感,真的调不来, 而flask_admin能让你把大多数的精力放在后端的逻辑上面。Apscheduler作为定时任务的框架引入,具体的实战过程后续介绍。

  • MongoEngine:这里介绍一下选用MongoEngine而不用pyMongo的原因,pymongo和mongoEngine都是flask可选的mongo数据库引擎,但是我们选用mongo数据库本身的原因就是想应对复杂数据的操作,而mongoEngine对于如list, dict这一类的数据处理简直无懈可击,如ReferenceField、ListField、EmbeddedDocument以及DictField等,重点是flask_admin对于mongoEngine的支持虽然存在瑕疵(后续介绍),但是真的已经非常好了,如下图对列表和字典的结合处理。(如下代码及实现效果)

class Header(EmbeddedDocument):
header_key = StringField(db_field="header_key")
header_value = StringField(db_field="header_value")
header_rule = DictField(db_field="header_rule", description=u"规则",
default={
"header1": {"h_optional": False, "h_check": True, "h_type": "integer", "h_range": [],
"h_len_min": 0, "h_len_max": 0},
"header2": {"h_optional": False, "h_check": True, "h_type": "integer", "h_range": [],
"h_len_min": 0, "h_len_max": 0}})

class Data(EmbeddedDocument):
data_key = StringField(db_field="data_key")
data_value = StringField(db_field="data_value")
data_rule = DictField(db_field="data_rule", description=u"规则",
default={"body1": {"b_default": "", "b_optional": False, "b_check": True, "b_type": "integer",
"b_range": [],
"b_len_min": 0, "b_len_max": 0},
"body2": {"b_default": "", "b_optional": False, "b_check": True, "b_type": "integer",
"b_range": [],
"b_len_min": 0, "b_len_max": 0}})

class Asset(EmbeddedDocument):
asset_key = StringField(db_field="asset_key")
asset_value = StringField(db_field="asset_value")

class InterfaceTestCaseForm(Document):
project = ReferenceField(ProjectForm)
creator = ReferenceField(TesterForm)
updater = ReferenceField(TesterForm)
interface = StringField(db_field="interface", description=u"接口地址",
validators=[DataRequired(message=u"接口地址不能为空")], max_length=100)
name = StringField(db_field="name", description=u"接口名称",
validators=[DataRequired(message=u"接口名称不能为空")], max_length=50)
interfaceType = StringField(db_field="interfaceType", description=u"接口请求类型",
validators=[DataRequired(message=u"接口请求类型不能为空")], max_length=50)
testPlanType = ReferenceField(TestPlanTypeForm, db_field="testPlanType")
internal = BooleanField(db_field="internal", default=True)
smoke = BooleanField(db_field="smoke", default=True)
regression = BooleanField(db_field="regression", default=True)
testResult = StringField(db_field="testResult")
headers = ListField(
EmbeddedDocumentField(Header),
max_length=30)
data = ListField(
EmbeddedDocumentField(Data),
max_length=30)
asset = ListField(
EmbeddedDocumentField(Asset),
max_length=30)
createTime = StringField(db_field='createTime', max_length=20) # TODO 修改改成DateField格式,参照TEST-57
updateTime = StringField(db_field='updateTime', max_length=20) # TODO 修改改成DateField格式,参照TEST-57
deleted = BooleanField(db_field="deleted", default=False)
meta = {
"collection": "interfaceTestCase",
'ordering': ['-updateTime']
}

  • Apscheduler:选择Apscheduler的理由,1. flask的支持。2. 与celery相比轻量。Apscheduler虽然轻量,但是对于定时任务的常规处理一点不含糊,同样支持持久化,并且可以通过对flask_admin框架的熟练使用做到定时任务的页面控制,实现过程后续介绍。 ThreadPoolExecutor:我们在实际的自动化测试工作中,往往都存在依赖关系并且顺序执行,但是对于测试平台来说,进程不可能一直被占用,需要尽快释放,让任务在后台执行。这个时候ThreadPoolExecutor就派上用场,可以完美的解决这个问题。同样可以执行异步任务的方案有很多,可以使用Redis、MQ或者Kafka的异步消息处理方式进行任务提交,不选用这些成熟的框架的原因就是让测试平台尽可能的轻量且好用。但是我们应该知道这些解决方案,怼一怼开发有的时候也是一种乐趣。

数据库选型

  • mongo: 测试平台更多的与测试相关,处理各种json数据,编写各种接口请求等。所以主要用到的就是Mongo数据库,还有另外一个原因就是mongo的collection*不需要提前创建,不需要*定义任何字段类型,只需要代码里面定义即可,简直不要太方便(如下图):
class ProjectForm(Document):
projectName = StringField(db_field="projectName", validators=[DataRequired(message=u"项目名不能为空")], )
projectPrefix = StringField(db_field="projectPrefix", validators=[DataRequired(message=u"项目前缀,请与testlink保持一致")])
projectTestOwner = ReferenceField(TesterForm)
projectTesters = ListField(ReferenceField(TesterForm)) # 增加项目测试人员
projectDomain = StringField(db_field="projectDomain", validators=[DataRequired(message=u"项目域名不能为空")], )
projectAuthUrl = StringField(db_field="projectAuthUrl", validators=[DataRequired(message=u"系统登录地址不能为空")], )
authUser = StringField(db_field="authUser", validators=[DataRequired(message=u"系统默认登录用户名不能为空")], )
authPassword = StringField(db_field="authPassword", validators=[DataRequired(message=u"系统默认登录密码不能为空")], )
projectDescription = StringField(db_field="projectDescription", validators=[DataRequired(message=u"项目描述不能为空")])
meta = {
"collection": "projects"
}

def __unicode__(self): # 解决对象引用过程中,前端看到的是可视化的名字,而不是对象名称。
return self.projectName
  • mysql:如数据业务简单但是存在各种耦合关联,避免脏数据产生等,mysql可以在数据库和表层面做很多限制工作,让程序运行更平稳,但是初始化或者前期工作就需要做很多,我们的平台基本上没有用。这个我用mysql的唯一原因就是保留flask_admin对mysql支持的尊重

  • Redis:这里我没有选择Redis的原因只有一个,让平台简洁化。Redis能够实现的功能,如内存存储、锁,完全可以用Mongo和环境变量来替代。这里要讲一下Redis能够作为锁的主要原因是Redis是单进程的,可以创建一个对象,但是同一时间只有一个进程操作这个对象,也就实现了锁的功效。实战过程中会教大家如何用环境变量来实现锁的功能。

其他相关联的工具

  • Jenkins:我面试过很多人,对Jenkins的了解都是停留在部署、执行自动化任务这些常规的功能。殊不知,Jenkins能实现的功能完全超乎你的想象,我曾经用Jenkins实现了一个外包人员的考核系统,用于考核外包人员对测试技能的掌握程度,这个功能的实现让我兴奋了一段时间。在这里,我用Jenkins主要是为了解决容器化部署带来的容器基础功能不完善以及测试平台一键部署问题。

  • JIRA:JIRA作为常规的缺陷管理工具,自然成为测试平台需要集成的工具,包括各种数据报表的展示等。

  • Testlink:Testlink作为用例管理工具的特点,轻量、操作方便、可以关联JIRA、域账号管理,这也是不用禅道的原因,当然禅道的本身的定位以及扩展功能的收费,也是我们奋战在各种绿色和破解版的这些人所不能接受的。

    以上就是对测试平台选型及框架工具的介绍,欢迎轻拍,重拍绝交。下一期将介绍测试平台的准备工作。下期见!

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞


一般才只有大佬才会这样说。我们这种菜比看看就好

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up