teprunner测试平台测试计划批量运行用例

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 本文开发内容 上一篇文章已经把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进行组件之间传值。**因此这里增加了添加计划、编辑计划等路由。 ...

测试的道与术

“道”说起来有点抽象,它相当于我们常说的“软技能”,包括价值观、认知能力、学习能力、创新能力、沟通协作等。不同层次的测试人员,道的差异也很大。初级测试人员,可能只知道测试是为了找出问题,能把需求上线即可。高级测试人员,也许会更加注重质量保障,从全局思考,既要保证线上不出问题,又要提高测试效率。他们的行为准则也不一样。有些人不注重资产沉淀,虽然能完成任务,但给人散乱的印象。有些人敬畏流程,严格按照标

从函数到包的Python代码层次

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 代码层次 Python是一门脚本语言,新建一个.py文件,写点代码,就可以跑起来了,无论放哪都可以。比如where.py文件: print("Where am I?") 那么问题来了,这是写在哪里的呢?为了一目了然,我们用“导游图”的视角来看看代码层次: 红色箭头指出了,是写在模块中的,原来一个.py文件就是一个模块。模块中可以写函数和类,模块可以放在包中。 函数 Python中最出名的函数一定是print()了,毕竟全世界都在用它say Hello World。Python函数其实和数学中的函数很像,比如y = f(x)。有函数名字、输入和输出。Python的函数结构如下: 函数通过def关键字来定义: def 函数名(参数列表): 函数体 参数列表有就有,无则无,多个参数用逗号分隔。例如: def hello() : print("Hello World!") hello() # 调用函数 def max(a, b): if a > b: return a else: return b a = 4 b = 5 print(max(a, b)) 调用函数,不需要再加def,直接函数名(参数列表)即可。参数既可以是变量,也可以是其他函数,只要能一一对应。return关键字用来返回值。return不是必需的,如果没有,那么函数会把内部代码全部都执行完再退出,如果有,函数会在return语句立刻退出,同时返回return语句的值,例如: # 可写函数说明 def sum( arg1, arg2 ): # 返回2个参数的和." total = arg1 + arg2 print ("函数内 : ", total) return total print("这里不会执行!") # 调用sum函数 total = sum( 10, 20 ) print ("函数外 : ", total) 类 为什么还要写类,函数它不香么?这个问题有点大,我只能简单解释一下,那就是因为,类是包括了函数的,如果有一天你发现函数不够用了,那么可以用类试试,哈哈哈。 ...

揭开DRF序列化技术的神秘面纱

在RESTful API中,接口返回的是JSON,JSON的内容对应的是数据库中的数据,DRF是通过序列化(Serialization)的技术,把数据模型转换为JSON的,反之,叫做反序列化(deserialization)。本文就来揭开2揭开DRF序列化技术的神秘面纱技术的神秘面纱。

原创pytest辅助工具tep0.9.1版本恢复项目初始化文件

最近几个月时间有些小伙伴问到为什么tep项目初始化文件没有了?

2026-01-07 · 1 分钟 · 58 字 · 测试开发刚哥 · 原创项目 · #Tep

AI原生应用的关键要素

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 2.1 模型 大模型扮演着大脑的角色,负责核心的理解、推理与生成任务。 2.1.1 模型分类 通用大模型:GPT、Claude、Qwen、DeepSeek、Gemini等通用大模型。 垂直领域模型:情感分析、语言翻译、意图分类等垂直领域模型。 通用大模型适合处理复杂、开放的任务,垂直领域模型适合处理简单、高频的任务。 2.1.2 模型能力和微调 模型的知识是固化的,需要不断地“喂”它,把这些信息结构化地描述清楚并且提供给它,它会在每次的交互中动态地理解你提供的内容,并结合自身的通用知识来完成内容的生成。 企业专属模型的训练和微调的成本是巨大的,它不仅包括高昂的计算资源费用,还需要海量的高质量标注数据、专业的算法团队以及漫长的开发周期。对于绝大多企业来说,投入与产出不成正比,很难持续。 2.1.3 如何选择模型 基于企业的实际业务场景,权衡任务复杂度、性能要求、开发成本和响应延迟等多个因素来综合选择和组合最合适的模型。 一个务实的策略是从顶配开始,逐步优化:先用能力最强的模型搭建原型以验证业务逻辑,再逐步将流程中的非核心、简单任务替换为更经济、更快速的小模型,最终找到成本与性能的最佳平衡点。 2.2 框架 Spring或Dubbo开发一个微服务应用,代码确定,容易形成标准化的框架。而开发一个AI Agent应用,LLM存在高度不确定性,设计模式的落地方式差异很大。 2.2.1 Agent设计模式 1、Chain of Thought(思维链) 让模型在回答前,把推理过程一步步写出来。 2、Self-Ask(自问自答) 让模型在回答时学会反问自己,把大问题拆成多个小问题,然后逐个回答。 3、ReAct(推理+行动) 在推理和外部行动(比如调用搜索引擎或API)之间交替进行。 4、Plan-and-Execute(计划与执行) 把任务拆成两个阶段,先生成计划,再逐步执行。 5、Tree of Thoughts(ToT,树状思维) 不是单线思维,而是生成多条思路分支,像树一样展开,再通过评估机制选出最佳分支。 6、Reflexion / Iterative Refinement(反思与迭代优化) Agent自我纠错,犯错后会总结失败原因,再带着反思尝试下一次。 7、Role-playing Agents(角色扮演式智能体) 把任务拆分给不同角色的Agent,通过对话协作完成任务。 这些模式不是互斥的,可以混搭使用。 其中ReAct已经被LangChain、LlamaIndex、Dify、Spring AI Alibaba等开发框架内置为基础框架。 2.2.2 Agent开发框架 1、从低代码到高代码 低代码平台是对于高代码的一层封装,其抽象层次很难满足所有场景,无法在性能、扩展性和复杂业务逻辑方面满足大规模生产的要求。 高代码是当下AI原生应用生产落地的主流形态,ADK、LangGraph、AutoGen、AgentScope、Spring AI Alibaba等框架,为开发者提供了面向Agent的编程接口。 2、高代码的演进 高代码模式本身经历了从ChatClient→Workflow→Agentic的演进过程。 ChatClient,一次单一的LLM调用。Workflow,将传统工作流转化为LLM节点编排。Agentic,提供面向Agent的API,并内置多种通用的协作模式(Pattern)。 3、零代码的愿景 尝试让用户完全通过自然语言即可驱动开发。目前处于探索与验证阶段,难以承担大规模生产任务。 2.3 提示词 在AI原生应用中,不再编写复杂的代码,而是使用更接近人类语言的方式与AI沟通,这就是Prompt(提示词)。提示词的质量很大程度上影响了AI应用的输出效果。 2.3.1 提示词是什么 提示词可以是一个具体的问题、一段描述、一组关键词,或是相关的上下文,不仅限于自然语言文本,还可以包含代码片段、数据格式说明,甚至是图像与文字相结合的多模态输入。 2.3.2 提示词质量=AI输出质量 大模型的输出质量,既要看大模型本身,也要看提示词是否清晰、完整、具体。比如,“写点关于人工智能的东西”,生成的内容是泛泛而谈。“以科技专栏作家的身份,撰写一篇800字左右的文章,探讨人工智能在医疗影像诊断中的最新应用、优势与挑战,并列举实例”,能生成一篇逻辑严谨、信息详实、专业度高的文章。 ...

Flask的Blueprints和Views

Flask的view函数是用来对请求作出响应的。单个URL能匹配到单个View,那么多个类似的URL,比如:

2026-01-06 · 2 分钟 · 258 字 · 测试开发刚哥 · Python生态 · #Flask

JavaWeb开发基础ServletConfig与ServletContext

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 ServletConfig package javax.servlet; import java.util.Enumeration; public interface ServletConfig { String getServletName(); ServletContext getServletContext(); String getInitParameter(String var1); Enumeration<String> getInitParameterNames(); } ServletConfig用于Servlet初始化时从web.xml获取配置信息。主要有以下方法: public String getInitParameter(String name): Returns the parameter value for the specified parameter name. public Enumeration getInitParameterNames(): Returns an enumeration of all the initialization parameter names. public String getServletName(): Returns the name of the servlet. public ServletContext getServletContext(): Returns an object of ServletContext. 示例,web.xml ...

Java官方笔记3Java语言基础

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 变量 Instance Variables (Non-Static Fields) 实例变量(非静态变量) 一个类可以创造多个实例,实例中的变量叫做实例变量,相互独立。 Class Variables (Static Fields) 类变量(静态变量) 对比来看,类变量就是类的变量,一个类只能有一份,不能复制,使用static关键字来定义类变量。 代码: public class MyVar { static int classVar = 1; // 类变量(静态变量) int instanceVar = 2; // 实例变量 } 对比Python来看就很清楚: class MyVar: class_var = 1 def __int__(self): instance_var = 2 Local Variables 局部变量 局部变量是由代码位置决定的,前面的实例变量和类变量,是放到Field位置,而局部变量是放到Method里面。也就是说,局部变量是放在方法的花括号里面的变量,并且只能在方法内部访问,不能被其他地方访问,这就是所谓的局部。 public class MyVar { static int classVar = 1; // 类变量(静态变量) int instanceVar = 2; // 实例变量 void method() { int localVar = 3; // 局部变量 } } Parameters 参数 ...

JMeter100个线程竟然只模拟出1个并发

线程组编辑区如下: