专栏文章 并发运行测试用例

cheetah · 2023年01月18日 · 最后由 cheetah 回复于 2023年01月28日 · 2360 次阅读

测试平台现有的运行方案

  1. api: 运行时通过和前置条件组成一个测试用例运行
  2. 测试套件:运行时通过和前置条件组成一个测试用例运行;每个测试套件数据可以互通,根据实际情况配置
  3. 测试用例:用例下引用测试套件,并与前置套件组成一个测试用例运行;每个测试用例数据独立,不依赖与其他用例
  4. 定时任务:定时任务下引用多个测试用例

从上面的方案看,测试套件没办法完全做到独立运行,每个测试用例都是相对独立的,所以可以从定时任务执行时增加对应的逻辑

实现并发运行

思路

  1. 实现多线程执行需要用到goroutine,好在 go 协程实现非常简单,只要在函数前面增加go就可以了,如:go func()
  2. 加入线程阻塞,保证测试报告不丢失
  3. 控制线程数,防止线程过多导致的资源占用过高
  4. 单例运行 plugin,否则每个用例会运行一个 plugin,导致内存占用过高,可参考性能问题排查及解决

代码

修改 hrp.Run 函数

func (r *HRPRunner) Run(testcases ...ITestCase) (interfacecase.ApiReport, error) {
    // 省略部分代码

    var runErr error
    // run testcase one by one
    var wg sync.WaitGroup
    // 通过channel的缓冲区设置协程数,最小2,最大为cpu线程数-2
    cpu := 2
    if runtime.NumCPU() >= 4 {
        cpu = runtime.NumCPU() - 2
    }
    intChan := make(chan int, cpu)
    defer close(intChan)
    // 添加计数器为用例个数
    wg.Add(len(testcases))

    for _, testCase := range testCases {
        // each testcase has its own case runner
        go func(testcase *TestCase) {
            defer wg.Done()
            intChan <- 1
            defer func() {
                <-intChan
            }()
            caseRunner, err := r.NewCaseRunner(testcase)
            if err != nil {
                log.Error().Err(err).Msg("[Run] init case runner failed")
                return
            }

            // release UI driver session
            defer func() {
                for _, client := range r.uiClients {
                    client.Driver.DeleteSession()
                }
            }()

            for it := caseRunner.parametersIterator; it.HasNext(); {
                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()
                caseSummary.CaseID = testcase.ID
                //for k, _ := range caseSummary.Records {
                //  caseSummary.Records[k].ValidatorsNumber = testcase.TestSteps[k].Struct().ValidatorsNumber
                //}

                //把header、Extract导出到上一级配置(caseRunner.testCase.Config)中
                //caseRunner.testCase.Config
                caseSummary.Name = testcase.Name
                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
                    }
                }
            }
        }(testCase)
    }
    // 阻塞,等待运行结束
    wg.Wait()
    s.Time.Duration = time.Since(s.Time.StartAt).Seconds()

    // save summary
    if r.saveTests {
        err := s.genSummary()
        if err != nil {
            return interfacecase.ApiReport{}, err
        }
    }

    // 省略部分代码
}

效果

按照 cpu 线程数 (i7 10700 8 核 16 线程)-2 进行执行,比单个运行时间节省了接近 90%,如果是全量回归,可以节省比较多的时间

往期文档

共收到 2 条回复 时间 点赞

go test 不是本身就是并行的吗..目录执行就好了

GodS+_+ 回复

go test 是可以并行执行的,但是需要生成 xx_test.go 文件,不适用于测试平台,测试平台是读取用例后调用 httprunner 执行

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册