tep0.9.5支持自定义扩展request
tep0.9.5更新了以下内容:
tep0.9.5更新了以下内容:
经过Python测试交流群的小伙伴群策群力,teprunner添加了一个重要功能,把PyCharm中的代码,通过Git同步到测试平台中,生成测试用例。这样,teprunner就成了一个名副其实的pytest脚本在线管理平台。
teprunner测试平台已经有一个多月没有更新了,主要原因是定时任务不够稳定,经过反复试错,找到了解决办法,这次终于稳定了。
大家好,我是刚哥。
本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 本文开发内容 在免费开源的测试平台中,teprunner的UI界面应该算得上第一梯度了,BUT,美中不足,在不同分辨率显示器(比如13.3寸小屏笔记本)下布局大的弹出窗口,表现不尽如人意。而这又是核心功能代码编辑的界面,比如用例编写、fixture编写,很有必要优化升级。除此之外还有滚动条的问题:竖向双滚动条和横向滚动条,也对体验造成影响。本文将对这些内容进行改造,具体如下: 编写用例页面,从弹窗改为子页面。 编写fixture页面,从弹窗改为子页面。 代码编辑区域高度自适应。 缩小左侧菜单,移动项目环境和新增按钮同一行,扩大编辑区面积。 消除所有双向滚动条。 消除所有横向滚动条。 语法说明添加环境变量和fixtures示例代码。 其他细节优化。 响应式是指同一套前端代码在不同浏览器(PC端、移动端等)都能适配,Element UI已经是响应式设计了,本文的响应式体现在代码编辑区行高根据window.innerHeight自动计算设定。 升级效果展示 左侧菜单栏变窄;添加环境变量和fixtures示例代码。 项目环境和新增按钮同一行显示,扩大有效内容可视面积。 fixtures的新增和编辑从弹窗改为子路由页面,代码编辑区高度自适应,纵向滚动条只出现一个。 消除横向滚动条;缩小了用例描述列以外其他列宽;替换操作按钮文字为图标。 用例的新增和编辑从弹窗改为子路由页面,代码编辑区高度自适应,纵向滚动条只出现一个。 用例运行结果的新增和编辑从弹窗改为子路由页面,代码编辑区高度自适应,纵向滚动条只出现一个。 测试计划的新增,编辑,运行结果也做了相同调整。 整体而言,有效信息的可视面积和可操作面积都尽可能扩大了! 关键技术点 前端代码修改文件如下: 比较繁琐,没有新知识,这里就不一一讲解了,而是选择用到的关键技术点来进行知识回顾: template添加<router-view></router-view> 主页面div添加v-if="$route.name === 'name'" 在router/index.js中添加children子路由 跳转页面前,存localStorage 新页面create()时,取localStorage this.$router.push跳转路由 div同行显示用float: left,紧跟的div换行用clear: both window.innerHeight获取屏幕高度 小结 本文完成了对teprunner测试平台前端UI界面的体验升级,已经适配13.3寸到27寸显示器,如果还在担心不好用的话,那么现在可以好好试试了。定时任务从目前优化效果来看,已经趋于稳定,不久就会和大家见面。 参考资料: Element UI图标按钮样式 https://www.jianshu.com/p/a81cd965943c
大家好,我是刚哥。
本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 本文开发内容 上一篇文章已经把pytest引入到测试平台中,通过多线程和多进程的方式,运行测试用例。有了这个基础,做批量运行用例的功能就很简单了,只需要前端传入一个CaseList即可。本文的后端代码是增删改查和复用run_case相关代码做个run_plan。前端代码将学习如何通过LocalStorage在非父子组件之间传递数据。具体开发内容如下: 测试计划增删改查 测试计划动态添加移除测试用例 运行测试计划,批量运行用例 计划运行结果、用例运行结果 编写后端代码 编辑teprunner/urls.py文件,添加测试计划的路由: 编辑teprunner/models.py文件,添加3张表的model: Plan存放测试计划,PlanCase存放计划和用例关联关系,PlanResult和CaseResult类似,这里之所以把单独出来一张结果表,是为了和用例结果CaseResult相互独立,防止计划结果覆盖用例结果,毕竟CaseResult一般是手工跑出来的,PlanResult一般是定时任务跑出来的,功能数据解耦后处理起来也更方便。 编辑teprunner/serializers.py文件,添加序列化器: 为了前端展示需要,这里额外关联了一些字段进行展示。尤其是: caseNum用例总条数,passedNum成功条数,failedNum失败条数,errorNum错误条数。成功定义为:只有passed,没有failed和error的用例。失败定义为:只有failed,没有error的用例。错误定义为:只要有error的用例。 本文还展示了,如果序列化器的某个字段只在存库时使用,返回响应时不展示,该怎么处理: 重写to_representation方法,把不需要返回的字段pop移出去。这里output在计划运行结果不需要,在用例运行结果才需要,为了减少传输数据,就在计划运行结果的接口响应中移出去了。 最后来写View,新增teprunner/views/plan.py文件: 重写list方法,添加查询条件。重写destroy方法,判断必须先删除关联测试用例,才能删除测试计划。 PlanCaseView类似: 添加add方法: 在添加用例时写入plan_case表。添加remove方法: 在移除用例时从plan_case表删除数据。继续: result视图用来返回计划运行结果,根据searchType来分别查询用例总条数、成功条数、失败条数、错误条数。 继续: plan.case_result和case.case_result类似,返回测试计划的每条用例的运行结果。 在测试计划添加用例的时候,有个用例列表,需要增加2个过滤条件,编辑teprunner/views/case.py文件: 第一个过滤条件是排除当前计划已经添加过的用例:exclude_plan_id由前端传入,根据PlanCase关联关系找到已添加的用例进行过滤;第二个查询条件是关键字:用例ID或用例描述,跟PlanCaseView.list()里面一样。 最后,编辑teprunner/views/run.py文件,增加批量运行用例的功能: 添加delete_plan_result方法,在运行用例前,先把已有结果删掉,注意这里的model是PlanResult。继续: 修改pytest_subprocess函数,添加plan_id字段。继续: 修改save_case_result函数,判断没有plan_id就存case_result表,有plan_id就存plan_result表。 复制run_case的代码为run_plan: 修改case相关代码为plan。值得注意的是中间那段代码,根据plan_id从PlanCase中找到关联用例,生成了case_list,这样就复用run_case的多线程和多进程代码,批量运行测试用例了。 编写前端代码 编辑src/router/index.js文件,添加测试计划相关路由: { path: "plan", name: "plan", meta: { title: "测试计划", }, component: () => import("@/views/teprunner/plan/PlanManagement.vue"), children: [ { path: "addPlan", name: "addPlan", meta: { title: "添加计划", }, component: () => import("@/views/teprunner/plan/PlanEditor"), }, { path: "editPlan", name: "editPlan", meta: { title: "编辑计划", }, component: () => import("@/views/teprunner/plan/PlanEditor"), }, { path: "caseList", name: "caseList", meta: { title: "用例列表", }, component: () => import("@/views/teprunner/plan/CaseList"), }, { path: "planResult", name: "planResult", meta: { title: "计划运行结果", }, component: () => import("@/views/teprunner/plan/PlanResult"), children: [ { path: "caseResult", name: "caseResult", meta: { title: "用例运行结果", }, component: () => import("@/views/teprunner/plan/CaseResult"), }, ], }, ], }, 跟之前做增删改查不一样的地方是:**在父组件中,不会import子组件,而是通过LocalStorage进行组件之间传值。**因此这里增加了添加计划、编辑计划等路由。 ...
最近几个月时间有些小伙伴问到为什么tep项目初始化文件没有了?
本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 本文开发内容 pytest登场!本文将在Django中引入pytest,原理是先执行tep startproject命令创建pytest项目文件,然后从数据库中拉取代码写入文件,最后调用pytest命令运行用例。为了提高运行效率,用例运行是并行的,采用了多线程和多进程,两个都有,这在最后有个单独小结进行比较完整的说明。因为用例运行是异步的,所以前端并不知道什么时候执行完才能拿到运行结果,可以发多个HTTP请求轮询,但这种方式并不优雅,本文将采用WebSocket来实现用例结果查询。具体内容为: cases/<int:pk>/run运行用例接口 ws/teprunner/cases/<int:case_id>/result/用例结果查询接口 projects/<int:pk>/export下载项目环境接口 前端添加WebSocket请求 知识点涉及有点多:tep、pytest、同步、异步、多线程、多进程、回调函数、WebSocket、长连接、全双工、ASGI、WSGI、打ZIP包、文件字节流传输。 编写后端代码 编辑requirements.txt,添加tep和channels: tep==0.6.9 channels==3.0.3 tep是用来创建pytest项目的,channels是用来实现WebSocket的。 编辑teprunner/urls.py文件,添加HTTP路由: 首先实现run接口,新建teprunner/views/run.py文件: 这是运行用例的主体流程: 第一步从请求中获取用例id、运行环境、运行人,这里演示了获取user数据的两种方式:接口传参和从token中解析。然后根据project_id,run_env,user_id定义了pytest项目的路径。 第二步使用tep startproject创建项目文件,清空fixtures和tests目录,目的有两个:一是清掉tep默认fixtures和示例cases,防止对平台产生干扰;二是保证每次运行目录都是干净的,就不用单独去处理前端手动删掉fixture/case后,文件残留的问题。然后从数据库中拉取环境变量、fixtures等数据更新文件。 起多个线程,分别执行用例,执行前先拉取用例代码写入文件,这里是单条用例运行,之所以要用for循环,是因为用例迟早是要批量执行,在设计时就考虑到,避免后面走弯路。然后删掉数据库运行结果,通过subprocess起子进程调用pytest命令,最后在线程的回调函数中根据pytest_result保存用例结果到数据库中。 注意!run_case接口不会直接返回结果,前端是用WebSocket来查询结果的。 图中很多函数和类是我封装的,一个一个拆解来看: 这里定义了Django中存放pytest项目的目录文件,project_temp_name是按照project_id、env_name、user_id来划分的,目的是让运行目录尽量隔离开来,不要相互影响,借鉴了Docker容器的思想,可以把这个目录视为用例运行容器。继续: tep startproject命令创建pytest项目,pytest文件有特定组织方式,比如conftest.py文件等,tep提供了脚手架一条命令创建项目结构。继续: fixture_env_vars.py里面存放了tep的环境变量,Django这里每次都从数据库的env_var表中获取数据,动态更新到文件里面。setdefault是个骚操作,这行代码等价于: if env_name in mapping.keys(): mapping[env_name][name] = value else: mapping[env_name] = {name: value} 继续: 分别从数据库中获取代码写入fixture文件和case文件,把前端传参的运行环境写入conf文件。继续: 清空fixtures目录,清空tests目录。继续: pull_tep_files是写环境变量,写fixture文件,写conf文件三步的集合,复用代码。pull_case_files通过yield定义为了生成器,它和list的区别是不会一次把所有数据产生到内存中,而是每次用的时候产生一次,节约内存开销。delete_case_result用于运行用例前删除case_result表里面已经存在的这条用例的数据。case_result按照用例id和运行人存的多条,每个运行人都有一条属于自己的运行数据,避免数据相互干扰,返给前端的是运行时间最新的那一条! 继续: subprocess.getoutput()可以执行shell命令并返回执行结果,这里就拿到了pytest控制台日志,这个函数是在线程池中异步执行的,主线程不能一直等待它执行,所以需要有个回调函数,等它自己执行完了去调用这个回调函数。save_case_result就是个回调函数,它的入参pytest_result等于pytest_subprocess函数返回的元组,拆包后就能拿到outout、cmd、case_id、run_env、run_user_nickname,从中解析出result和elapsed后,就可以存库了,无则新增,有则更新。 run接口做好了,再接口做下载环境接口,编辑teprunner/views/project.py: 打包的代码是从网上找的,把source_dir打包成zip_filename文件。继续: file_iterator函数也是网上找的,把二进制文件读取为字节流,传输给前端,需要使用StreamingHttpResponse对象并添加Content-Type和Content-Disposition。红框的代码跟run接口类似,区别在于目录换成了export_temp_dir(),且不包含测试用例,生成zip文件后会把导出临时目录删掉,防止冲突。 两个HTTP接口做完了,开始实现WebSocket。WSGI一种网关接口,是Python为了解决Web服务器端与客户端之间的通信问题而产生的,不支持WebSocket;ASGI是WSGI的扩展,意思是异步网关接口,支持WebSocket。编辑teprunnerbackend/urls.py文件: 添加了WebSocket路由。编辑teprunnerbackend/asgi.py文件: 添加websocket的URLRouter,http保持默认。编辑teprunnerbackend/settings.py文件: INSTALLED_APPS中添加channels,继续: 添加ASGI应用配置和CHANNEL配置。CHANNEL_LAYERS是一种通信系统,允许多个Consumer实例之间互相通信,以及与外部Django程序实现互通。学习版这里使用的InMemory。 生产中不建议使用InMemory,可能会有性能问题,而是应该使用Redis: CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { "hosts": [("127.0.0.1", 6379)], }, }, } 最后,编辑teprunner/views/case.py文件: ...
tep是一款测试工具,在pytest测试框架基础上集成了第三方包,提供项目脚手架,帮助以写Python代码方式,快速实现自动化项目落地。