HttpRunner v4 一条用例是怎么被执行的

这篇具有很好参考价值的文章主要介绍了HttpRunner v4 一条用例是怎么被执行的。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

HttpRunner 4.0版本,支持多种用例的编写格式:YAML/JSON/go test/pytest,其中后面两种格式我们都知道通过调用测试函数执行,那YAML/JSON这两种用例格式到底是怎样被运行的呢?下面我们一起分析一下

注意:以下代码被缩略过,只保留核心代码,框架版本:4.3.0

首先先从执行用例时的命令开始

hrp run case1 case2

var runCmd = &cobra.Command{
	Use:   "run $path...",
	Short: "run API test with go engine",
	Long:  `run yaml/json testcase files for API test`,
	Example: `  $ hrp run demo.json	# run specified json testcase file
  $ hrp run demo.yaml	# run specified yaml testcase file
  $ hrp run examples/	# run testcases in specified folder`,
	Args: cobra.MinimumNArgs(1),
	PreRun: func(cmd *cobra.Command, args []string) {
		setLogLevel(logLevel)
	},
    // 【重点】:执行命令后调用函数
	RunE: func(cmd *cobra.Command, args []string) error {
        // 【疑问】:为什么存放用例路径的数组类型是:hrp.ITestCase
		var paths []hrp.ITestCase
        // 编辑命令参数获取所有待执行用例的路径
		for _, arg := range args {
			path := hrp.TestCasePath(arg)
			paths = append(paths, &path)
		}
        // 创建运行器
		runner := makeHRPRunner()
        // 调用执行函数
		return runner.Run(paths...)
	},
}

总结

在执行命令后,代码处理了

  1. 创建运行器,根据命令行参数初始化一个运行器

  2. 调用执行函数,将待执行用例作为参数传入

[疑问]命令行传入的参数是一个用例路径,为什么接收数组类型是: hrp.ITestCase

其实hrp.ITestCase是一个接口,由于框架本身要支持多种用例类型:go test/文件类型/curl... 需要将不同类型的用例转换成一个相同结构在运行,ITestCase 接口就是定义一个规范来实现统一结构。

// 不同的用例格式,只需要实现ITestCase接口定义的两个方法即可通过运行器运行
type ITestCase interface {
	GetPath() string
	ToTestCase() (*TestCase, error)
}

而在接收到命令行参数后有将参数转换:hrp.TestCasePath(arg)

TestCasePath 是 string 类型的别名,同时实现了ITestCase接口,所以用例路径可以转为:hrp.ITestCase

type TestCasePath string

func (path *TestCasePath) GetPath() string {
	return fmt.Sprintf("%v", *path)
}

// ToTestCase loads testcase path and convert to *TestCase
func (path *TestCasePath) ToTestCase() (*TestCase, error) {
	tc := &TCase{}
	casePath := path.GetPath()
	err := builtin.LoadFile(casePath, tc)
	if err != nil {
		return nil, err
	}
	return tc.ToTestCase(casePath)
}

执行命令后调用Run方法进行处理

func (r *HRPRunner) Run(testcases ...ITestCase) error {
	// ··· 缩略代码
    
	// 初始化执行摘要,用于存储执行结果
	s := newOutSummary()

	// 【重点】加载测试用例
	testCases, err := LoadTestCases(testcases...)
	if err != nil {
		log.Error().Err(err).Msg("failed to load testcases")
		return err
	}

	// ··· 缩略代码
	

	var runErr error
    
	// 遍历每一条用例
	for _, testcase := range testCases {
        
		// 【重点】每一条用例创建一个独立的运行器
		caseRunner, err := r.NewCaseRunner(testcase)
		if err != nil {
			log.Error().Err(err).Msg("[Run] init case runner failed")
			return err
		}

		// ... 缩略代码
		

        // 【重点】迭代器,负责参数化迭代
        // 【疑问】当用例没有参数化时,迭代器会运行吗?
		for it := caseRunner.parametersIterator; it.HasNext(); {
			// case runner can run multiple times with different parameters
			// each run has its own session runner

            // 【重点】为每一次参数迭代创建一个会话运行器
			sessionRunner := caseRunner.NewSession()
            
            // 【重点】启动会话运行器
			err1 := sessionRunner.Start(it.Next())
			if err1 != nil {
				log.Error().Err(err1).Msg("[Run] run testcase failed")
				runErr = err1
			}

            // 【重点】获取会话的运行结果,
			caseSummary, err2 := sessionRunner.GetSummary()
			s.appendCaseSummary(caseSummary)
			if err2 != nil {
				log.Error().Err(err2).Msg("[Run] get summary failed")
				if err1 != nil {
					runErr = errors.Wrap(err1, err2.Error())
				} else {
					runErr = err2
				}
			}

            // 运行错误时跳出当前迭代
			if runErr != nil && r.failfast {
				break
			}
		}
	}

    // 获取运行时长
	s.Time.Duration = time.Since(s.Time.StartAt).Seconds()

	// 【重点】保存测试结果
	if r.saveTests {
		err := s.genSummary()
		if err != nil {
			return err
		}
	}

	// 【重点】生成测试报告
	if r.genHTMLReport {
		err := s.genHTMLReport()
		if err != nil {
			return err
		}
	}

	return runErr
}

总结

Run方法为实际执行用例的入口

  1. 加载测试用例统一处理返回 []*TestCase
  2. 为每一条用例创建一个独立的运行器
  3. 遍历参数,创建迭代器进行迭代遍历
  4. 为每次迭代参数创建一个会话运行器
  5. 启动会话运行器,执行用例
  6. 采集每个迭代会话的运行结果
  7. 保存运行结果
  8. 生成测试报告

[疑问]如果用例中没有设置参数化,迭代器还会运行吗?

答案肯定是会运行,我们在实际使用中肯定遇到过不需要参数化的场景,那在没有参数化时迭代器是怎样执行的呢?

通过分析下面这块代码,发现其实想要执行用例,只需要满足it.HasNext()即可

// 满足:it.HasNext() 即可进入循环
for it := caseRunner.parametersIterator; it.HasNext(); {
			// case runner can run multiple times with different parameters
			// each run has its own session runner
			sessionRunner := caseRunner.NewSession()
			err1 := sessionRunner.Start(it.Next())
			if err1 != nil {
				log.Error().Err(err1).Msg("[Run] run testcase failed")
				runErr = err1
			}
			caseSummary, err2 := sessionRunner.GetSummary()
			s.appendCaseSummary(caseSummary)
			if err2 != nil {
				log.Error().Err(err2).Msg("[Run] get summary failed")
				if err1 != nil {
					runErr = errors.Wrap(err1, err2.Error())
				} else {
					runErr = err2
				}
			}

			if runErr != nil && r.failfast {
				break
			}
		}

可以看到只需要满足几个条件,HasNext 将会返回true

  1. iter.limit == -1
  2. iter.hasNext == true && iter.index < iter.limit
func (iter *ParametersIterator) HasNext() bool {
	if !iter.hasNext {
		return false
	}

	// unlimited mode
	if iter.limit == -1 {
		return true
	}

	// reached limit
	if iter.index >= iter.limit {
		// cache query result
		iter.hasNext = false
		return false
	}

	return true
}

想知道上述条件是怎么设置的还要从初始化ParametersIterator开始,通过下面代码分析,在没有设置参数化时初始化代码刚好满足条件2.所以HasNext的判断是可以通过的

func newParametersIterator(parameters map[string]Parameters, config *TParamsConfig) *ParametersIterator {
	if config == nil {
		config = &TParamsConfig{}
	}

    // 【重点】初始化 ParametersIterator 此时:hasNext == true index == 0
	iterator := &ParametersIterator{
		data:                 parameters,
		hasNext:              true,
		sequentialParameters: nil,
		randomParameterNames: nil,
		limit:                config.Limit,
		index:                0,
	}

    // 【重点】当parameters的长度等于0的时候 limit = 1
	if len(parameters) == 0 {
		iterator.data = map[string]Parameters{}
		iterator.limit = 1
		return iterator
	}

	// ... 省略代码

	return iterator
}

此时满足了it.HasNext(),代码继续执行会发现在Start()函数执行的时候,还传入了it.Next()

既然都没有参数化,那it.Next()会发生什么事呢?文章来源地址https://www.toymoban.com/news/detail-692472.html

func (iter *ParametersIterator) Next() map[string]interface{} {
	iter.Lock()
	defer iter.Unlock()

	if !iter.hasNext {
		return nil
	}

	var selectedParameters map[string]interface{}
    // 【重点】初始化时 sequentialParameters 为nil 此时获取长度 == 0 满足条件
	if len(iter.sequentialParameters) == 0 {
        //【重点】 selectedParameters 初始化为一个空map
		selectedParameters = make(map[string]interface{})
        
	} else if iter.index < len(iter.sequentialParameters) {
		selectedParameters = iter.sequentialParameters[iter.index]
	} else {
		// loop back to the first sequential parameter
		index := iter.index % len(iter.sequentialParameters)
		selectedParameters = iter.sequentialParameters[index]
	}

	// 【重点】randomParameterNames 初始化时也为nil 此时不进入循环
	for _, paramName := range iter.randomParameterNames {
		randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
		randIndex := randSource.Intn(len(iter.data[paramName]))
		for k, v := range iter.data[paramName][randIndex] {
			selectedParameters[k] = v
		}
	}

    //【重点】 index ++ 后 保证下次调用it.HasNext() == false
	iter.index++
	if iter.limit > 0 && iter.index >= iter.limit {
		iter.hasNext = false
	}

    // 【重点】最终会返回一个空map
	return selectedParameters
}

到了这里,关于HttpRunner v4 一条用例是怎么被执行的的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一篇短文让你知道软件测试中的测试用例是啥

    目录 一.什么是测试用例 1.测试用例是软件测试的核心 2.评估测试的基准 3.测试用例的八大要素 二、测试用例评审有什么用 三、测试用例评审的目的 四、用例的设计方法                 1.黑盒测试的方法                 2.等价类划分的设计用例思路:   

    2024年02月08日
    浏览(28)
  • Selenium+python怎么搭建自动化测试框架、执行自动化测试用例、生成自动化测试报告、发送测试报告邮件

    本人在网上查找了很多做自动化的教程和实例,偶然的一个机会接触到了selenium,觉得非常好用。后来就在网上查阅各种selenium的教程,但是网上的东西真的是太多了,以至于很多东西参考完后无法系统的学习和应用。 以下整理的只是书中自动化项目的知识内容,介绍怎么搭

    2024年02月05日
    浏览(49)
  • 一条指令在CPU里如何执行?

    本人才疏学浅,如果我的内容有明显的错误,或者有疑问的地方,衷心希望您能和我联系,给出建议和指导,或者与我交流相关知识。我会对你表示无限的感激!欢迎指正! “灵魂画手”上线: 老师上周单独布置给我的任务:让我去搞清楚“ 一条指令在cpu里是如何执行的呢

    2023年04月15日
    浏览(39)
  • MySQL执行流程_执行一条select语句,期间发生了什么

    MySQL执行流程 server层负责建立连接、分析和执行SQL 包括连接器、查询缓存、解析器、预处理器、优化器、执行器等,所有内置函数和所有跨存储引擎的功能在该层实现 存储引擎层负责数据的存储和提取 索引数据结构就是由存储引擎层实现,不同的存储引擎支持的索引类型也

    2024年02月04日
    浏览(40)
  • linux 下执行命令需要再次输入密码才能执行,如何在一条命令中执行

    当我在 linux 下执行如下命令时 需要再次输入cc 用户的密码才能运行, 现在我需要在一条命令中执行,不单独去输入密码执行命令 这样就可以使用一条命令执行 cc 用户下面的脚本了

    2024年02月11日
    浏览(59)
  • 01 | 一条 SQL 查询语句是如何执行的?

    以下内容出自 《MySQL 实战 45 讲》 一条 SQL 查询语句是如何执行的? 下面是 MySQL 的基本架构示意图,从中可以清楚地看到 SQL 语句在 MySQL 的各个功能模块中的执行过程。 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。 Server 层包括连接器、查询缓存、分析器、优化器

    2024年02月10日
    浏览(33)
  • MySQL 一条SQL语句是如何执行的?

    ​ 所以今天我们把MySQL拆解一下,看看里边有哪些零件。下边是MySQL的基本架构示意图。 大体来说,MySQL分为Server层和存储引擎两部分。 Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数

    2024年02月01日
    浏览(44)
  • Linux 反复执行一条命令--watch命令

    在Linux中,我们可能会需要反复的执行一个命令以查看状态的变化,比如之前我在启动一个项目的时候没有什么反应,于是我便自己写脚本反复 curl 这个端口,下面用输出 Hello World 举出这个例子: 我们的需求是非常简单地,只需要不断地间隔一秒输出 Hello World ,但是却需要

    2023年04月21日
    浏览(36)
  • 一条SQL在MySQL中是如何执行的

    MySQL的内部组件结构 连接数据库做了什么? 建立链接的时候需要将host,user,pwd,在user表中校验用户(用户名和密码)是否正确。每个链接链接到服务器端之后会给链接开辟一个专属的空间。这个空间存储了很多这个链接需要使用的操作。并且会把这个用户的权限都放到这个空间

    2023年04月09日
    浏览(31)
  • MYSQL执行一条SELECT语句的具体流程

    昨天CSDN突然抽风 我一个ctrl+z把整篇文章给撤掉了还不能复原 直接心态崩了不想写了 不过这部分果然还是很重要,还是写出来吧 这里面总共有两层结构Server层 储存引擎 Server 层负责建立连接、分析和执行 SQL 。MySQL 大多数的核心功能模块都在这实现,主要包括连接器,查询

    2024年02月12日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包