真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

HttpRunnerv4一條用例是怎么被執(zhí)行的-創(chuàng)新互聯(lián)

HttpRunner 4.0版本,支持多種用例的編寫格式:YAML/JSON/go test/pytest,其中后面兩種格式我們都知道通過調(diào)用測試函數(shù)執(zhí)行,那YAML/JSON這兩種用例格式到底是怎樣被運行的呢?下面我們一起分析一下

創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來,先為魏都等服務(wù)建站,魏都等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為魏都企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

注意:以下代碼被縮略過,只保留核心代碼,框架版本:4.3.0

首先先從執(zhí)行用例時的命令開始

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)
	},
    // 【重點】:執(zhí)行命令后調(diào)用函數(shù)
	RunE: func(cmd *cobra.Command, args []string) error {
        // 【疑問】:為什么存放用例路徑的數(shù)組類型是:hrp.ITestCase
		var paths []hrp.ITestCase
        // 編輯命令參數(shù)獲取所有待執(zhí)行用例的路徑
		for _, arg := range args {
			path := hrp.TestCasePath(arg)
			paths = append(paths, &path)
		}
        // 創(chuàng)建運行器
		runner := makeHRPRunner()
        // 調(diào)用執(zhí)行函數(shù)
		return runner.Run(paths...)
	},
}
總結(jié) 在執(zhí)行命令后,代碼處理了
  1. 創(chuàng)建運行器,根據(jù)命令行參數(shù)初始化一個運行器

  2. 調(diào)用執(zhí)行函數(shù),將待執(zhí)行用例作為參數(shù)傳入

[疑問]命令行傳入的參數(shù)是一個用例路徑,為什么接收數(shù)組類型是: hrp.ITestCase

其實hrp.ITestCase是一個接口,由于框架本身要支持多種用例類型:go test/文件類型/curl... 需要將不同類型的用例轉(zhuǎn)換成一個相同結(jié)構(gòu)在運行,ITestCase 接口就是定義一個規(guī)范來實現(xiàn)統(tǒng)一結(jié)構(gòu)。

// 不同的用例格式,只需要實現(xiàn)ITestCase接口定義的兩個方法即可通過運行器運行
type ITestCase interface {
	GetPath() string
	ToTestCase() (*TestCase, error)
}

而在接收到命令行參數(shù)后有將參數(shù)轉(zhuǎn)換:hrp.TestCasePath(arg)

TestCasePath 是 string 類型的別名,同時實現(xiàn)了ITestCase接口,所以用例路徑可以轉(zhuǎn)為: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)
}
執(zhí)行命令后調(diào)用Run方法進行處理
func (r *HRPRunner) Run(testcases ...ITestCase) error {
	// ··· 縮略代碼
    
	// 初始化執(zhí)行摘要,用于存儲執(zhí)行結(jié)果
	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 {
        
		// 【重點】每一條用例創(chuàng)建一個獨立的運行器
		caseRunner, err := r.NewCaseRunner(testcase)
		if err != nil {
			log.Error().Err(err).Msg("[Run] init case runner failed")
			return err
		}

		// ... 縮略代碼
		

        // 【重點】迭代器,負(fù)責(zé)參數(shù)化迭代
        // 【疑問】當(dāng)用例沒有參數(shù)化時,迭代器會運行嗎?
		for it := caseRunner.parametersIterator; it.HasNext(); {
			// case runner can run multiple times with different parameters
			// each run has its own session runner

            // 【重點】為每一次參數(shù)迭代創(chuàng)建一個會話運行器
			sessionRunner := caseRunner.NewSession()
            
            // 【重點】啟動會話運行器
			err1 := sessionRunner.Start(it.Next())
			if err1 != nil {
				log.Error().Err(err1).Msg("[Run] run testcase failed")
				runErr = err1
			}

            // 【重點】獲取會話的運行結(jié)果,
			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
				}
			}

            // 運行錯誤時跳出當(dāng)前迭代
			if runErr != nil && r.failfast {
				break
			}
		}
	}

    // 獲取運行時長
	s.Time.Duration = time.Since(s.Time.StartAt).Seconds()

	// 【重點】保存測試結(jié)果
	if r.saveTests {
		err := s.genSummary()
		if err != nil {
			return err
		}
	}

	// 【重點】生成測試報告
	if r.genHTMLReport {
		err := s.genHTMLReport()
		if err != nil {
			return err
		}
	}

	return runErr
}
總結(jié) Run方法為實際執(zhí)行用例的入口
  1. 加載測試用例統(tǒng)一處理返回 []*TestCase
  2. 為每一條用例創(chuàng)建一個獨立的運行器
  3. 遍歷參數(shù),創(chuàng)建迭代器進行迭代遍歷
  4. 為每次迭代參數(shù)創(chuàng)建一個會話運行器
  5. 啟動會話運行器,執(zhí)行用例
  6. 采集每個迭代會話的運行結(jié)果
  7. 保存運行結(jié)果
  8. 生成測試報告
[疑問]如果用例中沒有設(shè)置參數(shù)化,迭代器還會運行嗎?

答案肯定是會運行,我們在實際使用中肯定遇到過不需要參數(shù)化的場景,那在沒有參數(shù)化時迭代器是怎樣執(zhí)行的呢?

通過分析下面這塊代碼,發(fā)現(xiàn)其實想要執(zhí)行用例,只需要滿足it.HasNext()即可

// 滿足:it.HasNext() 即可進入循環(huán)
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
}

想知道上述條件是怎么設(shè)置的還要從初始化ParametersIterator開始,通過下面代碼分析,在沒有設(shè)置參數(shù)化時初始化代碼剛好滿足條件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,
	}

    // 【重點】當(dāng)parameters的長度等于0的時候 limit = 1
	if len(parameters) == 0 {
		iterator.data = map[string]Parameters{}
		iterator.limit = 1
		return iterator
	}

	// ... 省略代碼

	return iterator
}

此時滿足了it.HasNext(),代碼繼續(xù)執(zhí)行會發(fā)現(xiàn)在Start()函數(shù)執(zhí)行的時候,還傳入了it.Next()

既然都沒有參數(shù)化,那it.Next()會發(fā)生什么事呢?

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 此時不進入循環(huán)
	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 ++ 后 保證下次調(diào)用it.HasNext() == false
	iter.index++
	if iter.limit >0 && iter.index >= iter.limit {
		iter.hasNext = false
	}

    // 【重點】最終會返回一個空map
	return selectedParameters
}

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


本文名稱:HttpRunnerv4一條用例是怎么被執(zhí)行的-創(chuàng)新互聯(lián)
瀏覽地址:http://weahome.cn/article/cdgdoi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部