update: httprunner make v4

This commit is contained in:
buyuxiang
2022-05-26 16:04:46 +08:00
parent d288d672f8
commit ecd61c815f
10 changed files with 95 additions and 219 deletions
+2 -3
View File
@@ -241,7 +241,6 @@ func (c *TCaseConverter) ToGoTest() (string, error) {
type ICaseConverter interface { type ICaseConverter interface {
Struct() *TCaseConverter Struct() *TCaseConverter
ToJSON() (string, error) ToJSON() (string, error)
ToJSONTemp() (string, error)
ToYAML() (string, error) ToYAML() (string, error)
ToGoTest() (string, error) ToGoTest() (string, error)
ToPyTest() (string, error) ToPyTest() (string, error)
@@ -345,8 +344,8 @@ func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error)
} }
func convertToPyTest(iCaseConverter ICaseConverter) (string, error) { func convertToPyTest(iCaseConverter ICaseConverter) (string, error) {
// convert to temporary json testcase compatible with python engine style // convert to temporary json testcase
jsonPath, err := iCaseConverter.ToJSONTemp() jsonPath, err := iCaseConverter.ToJSON()
inputType := iCaseConverter.Struct().InputType inputType := iCaseConverter.Struct().InputType
if err != nil { if err != nil {
return "", errors.Wrapf(err, "(%s -> pytest step 1) failed to convert to temporary json testcase", inputType.String()) return "", errors.Wrapf(err, "(%s -> pytest step 1) failed to convert to temporary json testcase", inputType.String())
+1 -31
View File
@@ -384,19 +384,6 @@ func (c *ConverterHAR) ToJSON() (string, error) {
return jsonPath, nil return jsonPath, nil
} }
func (c *ConverterHAR) ToJSONTemp() (string, error) {
tCase, err := c.makeTestCaseTemp()
if err != nil {
return "", err
}
jsonPath := c.converter.genOutputPath(suffixJSON)
err = builtin.Dump2JSON(tCase, jsonPath)
if err != nil {
return "", err
}
return jsonPath, nil
}
func (c *ConverterHAR) ToYAML() (string, error) { func (c *ConverterHAR) ToYAML() (string, error) {
tCase, err := c.makeTestCase() tCase, err := c.makeTestCase()
if err != nil { if err != nil {
@@ -429,24 +416,7 @@ func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) {
Config: c.prepareConfig(), Config: c.prepareConfig(),
TestSteps: teststeps, TestSteps: teststeps,
} }
err = tCase.MakeCompat2GoEngine() err = tCase.MakeCompat()
if err != nil {
return nil, err
}
return tCase, nil
}
func (c *ConverterHAR) makeTestCaseTemp() (*hrp.TCase, error) {
teststeps, err := c.prepareTestSteps()
if err != nil {
return nil, err
}
tCase := &hrp.TCase{
Config: c.prepareConfig(),
TestSteps: teststeps,
}
err = tCase.MakeCompat2PyEngine()
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -26
View File
@@ -37,19 +37,6 @@ func (c *ConverterJSON) ToJSON() (string, error) {
return jsonPath, nil return jsonPath, nil
} }
func (c *ConverterJSON) ToJSONTemp() (string, error) {
testCase, err := c.makeTestCaseTemp()
if err != nil {
return "", err
}
jsonPath := c.converter.genOutputPath(suffixJSON)
err = builtin.Dump2JSON(testCase, jsonPath)
if err != nil {
return "", err
}
return jsonPath, nil
}
func (c *ConverterJSON) ToYAML() (string, error) { func (c *ConverterJSON) ToYAML() (string, error) {
testCase, err := c.makeTestCase() testCase, err := c.makeTestCase()
if err != nil { if err != nil {
@@ -91,19 +78,7 @@ func (c *ConverterJSON) makeTestCase() (*hrp.TCase, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tCase.MakeCompat2GoEngine() err = tCase.MakeCompat()
if err != nil {
return nil, err
}
return tCase, nil
}
func (c *ConverterJSON) makeTestCaseTemp() (*hrp.TCase, error) {
tCase, err := makeTestCaseFromJSONYAML(c)
if err != nil {
return nil, err
}
err = tCase.MakeCompat2PyEngine()
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -34
View File
@@ -144,19 +144,6 @@ func (c *ConverterPostman) ToJSON() (string, error) {
return jsonPath, nil return jsonPath, nil
} }
func (c *ConverterPostman) ToJSONTemp() (string, error) {
testCase, err := c.makeTestCaseTemp()
if err != nil {
return "", err
}
jsonPath := c.converter.genOutputPath(suffixJSON)
err = builtin.Dump2JSON(testCase, jsonPath)
if err != nil {
return "", err
}
return jsonPath, nil
}
func (c *ConverterPostman) ToYAML() (string, error) { func (c *ConverterPostman) ToYAML() (string, error) {
testCase, err := c.makeTestCase() testCase, err := c.makeTestCase()
if err != nil { if err != nil {
@@ -192,27 +179,7 @@ func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) {
Config: c.prepareConfig(casePostman), Config: c.prepareConfig(casePostman),
TestSteps: teststeps, TestSteps: teststeps,
} }
err = tCase.MakeCompat2GoEngine() err = tCase.MakeCompat()
if err != nil {
return nil, err
}
return tCase, nil
}
func (c *ConverterPostman) makeTestCaseTemp() (*hrp.TCase, error) {
casePostman, err := c.load()
if err != nil {
return nil, err
}
teststeps, err := c.prepareTestSteps(casePostman)
if err != nil {
return nil, err
}
tCase := &hrp.TCase{
Config: c.prepareConfig(casePostman),
TestSteps: teststeps,
}
err = tCase.MakeCompat2PyEngine()
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -26
View File
@@ -34,19 +34,6 @@ func (c *ConverterYAML) ToJSON() (string, error) {
return jsonPath, nil return jsonPath, nil
} }
func (c *ConverterYAML) ToJSONTemp() (string, error) {
testCase, err := c.makeTestCaseTemp()
if err != nil {
return "", err
}
jsonPath := c.converter.genOutputPath(suffixJSON)
err = builtin.Dump2JSON(testCase, jsonPath)
if err != nil {
return "", err
}
return jsonPath, nil
}
func (c *ConverterYAML) ToYAML() (string, error) { func (c *ConverterYAML) ToYAML() (string, error) {
testCase, err := c.makeTestCase() testCase, err := c.makeTestCase()
if err != nil { if err != nil {
@@ -74,19 +61,7 @@ func (c *ConverterYAML) makeTestCase() (*hrp.TCase, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tCase.MakeCompat2GoEngine() err = tCase.MakeCompat()
if err != nil {
return nil, err
}
return tCase, nil
}
func (c *ConverterYAML) makeTestCaseTemp() (*hrp.TCase, error) {
tCase, err := makeTestCaseFromJSONYAML(c)
if err != nil {
return nil, err
}
err = tCase.MakeCompat2PyEngine()
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -1
View File
@@ -47,7 +47,7 @@ func (path *APIPath) ToAPI() (*API, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = convertValidatorCompat2GoEngine(api.Validators) err = convertCompatValidator(api.Validators)
convertExtract(api.Extract) convertExtract(api.Extract)
return api, err return api, err
} }
+6 -77
View File
@@ -64,7 +64,7 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) {
return nil, errors.New("incorrect testcase file format, expected config in file") return nil, errors.New("incorrect testcase file format, expected config in file")
} }
err = tc.MakeCompat2GoEngine() err = tc.MakeCompat()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -173,11 +173,11 @@ type TCase struct {
TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` TestSteps []*TStep `json:"teststeps" yaml:"teststeps"`
} }
// MakeCompat2GoEngine converts TCase compatible with Golang engine style // MakeCompat converts TCase compatible with Golang engine style
func (tc *TCase) MakeCompat2GoEngine() (err error) { func (tc *TCase) MakeCompat() (err error) {
defer func() { defer func() {
if p := recover(); p != nil { if p := recover(); p != nil {
err = fmt.Errorf("[MakeCompat2GoEngine] convert compat testcase error: %v", p) err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p)
} }
}() }()
for _, step := range tc.TestSteps { for _, step := range tc.TestSteps {
@@ -194,7 +194,7 @@ func (tc *TCase) MakeCompat2GoEngine() (err error) {
} }
// 2. deal with validators compatibility // 2. deal with validators compatibility
err = convertValidatorCompat2GoEngine(step.Validators) err = convertCompatValidator(step.Validators)
if err != nil { if err != nil {
return err return err
} }
@@ -205,7 +205,7 @@ func (tc *TCase) MakeCompat2GoEngine() (err error) {
return nil return nil
} }
func convertValidatorCompat2GoEngine(Validators []interface{}) (err error) { func convertCompatValidator(Validators []interface{}) (err error) {
for i, iValidator := range Validators { for i, iValidator := range Validators {
if _, ok := iValidator.(Validator); ok { if _, ok := iValidator.(Validator); ok {
continue continue
@@ -272,77 +272,6 @@ func convertCheckExpr(checkExpr string) string {
return strings.Join(checkItems, ".") return strings.Join(checkItems, ".")
} }
// MakeCompat2PyEngine converts TCase compatible with Python engine style
func (tc *TCase) MakeCompat2PyEngine() (err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("[MakeCompat2PyEngine] convert compat testcase error: %v", p)
}
}()
for _, step := range tc.TestSteps {
// 1. deal with request body compatibility
if step.Request != nil && step.Request.Body != nil {
if strings.HasPrefix(step.Request.Headers["Content-Type"], "application/json") {
step.Request.Json = step.Request.Body
step.Request.Body = nil
continue
}
step.Request.Data = step.Request.Body
step.Request.Body = nil
}
// 2. deal with validators compatibility
err = convertValidatorCompat2PyEngine(step.Validators)
if err != nil {
return err
}
}
return
}
func convertValidatorCompat2PyEngine(Validators []interface{}) (err error) {
for i, iValidator := range Validators {
if v, ok := iValidator.(Validator); ok {
var iValidatorContent []interface{}
iValidatorContent = append(iValidatorContent, v.Check)
iValidatorContent = append(iValidatorContent, v.Expect)
newValidatorMap := make(map[string]interface{})
newValidatorMap[v.Assert] = iValidatorContent
Validators[i] = newValidatorMap
continue
}
validatorMap := iValidator.(map[string]interface{})
// validator check priority: Python > Golang engine style
if len(validatorMap) == 1 {
// Python engine style
for _, iValidatorContent := range validatorMap {
validatorContent := iValidatorContent.([]interface{})
if len(validatorContent) > 3 {
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
}
continue
}
iCheck, checkExisted := validatorMap["check"]
iAssert, assertExisted := validatorMap["assert"]
iExpect, expectExisted := validatorMap["expect"]
if checkExisted && assertExisted && expectExisted {
// Golang engine style
var validatorContent []interface{}
validatorContent = append(validatorContent, iCheck)
validatorContent = append(validatorContent, iExpect)
if iMsg, msgExisted := validatorMap["msg"]; msgExisted {
validatorContent = append(validatorContent, iMsg)
}
newValidatorMap := make(map[string]interface{})
newValidatorMap[iAssert.(string)] = validatorContent
Validators[i] = newValidatorMap
continue
}
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
return
}
func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
testCases := make([]*TestCase, 0) testCases := make([]*TestCase, 0)
+23 -9
View File
@@ -1,5 +1,5 @@
""" """
This module handles compatibility issues between testcase format v2 and v3. This module handles compatibility issues between testcase format v2, v3 and v4.
""" """
import os import os
import sys import sys
@@ -14,9 +14,8 @@ from httprunner.utils import sort_dict_by_custom_order
def convert_variables( def convert_variables(
raw_variables: Union[Dict, Text], test_path: Text raw_variables: Union[Dict, Text], test_path: Text
) -> Dict[Text, Any]: ) -> Dict[Text, Any]:
if isinstance(raw_variables, Dict): if isinstance(raw_variables, Dict):
return raw_variables return raw_variables
@@ -33,6 +32,18 @@ def convert_variables(
) )
def _convert_request(request: Dict) -> Dict:
if "body" in request:
content_type = ""
if "headers" in request and "Content-Type" in request["headers"]:
content_type = request["headers"]["Content-Type"]
if content_type.startswith("application/json"):
request["json"] = request.pop("body")
else:
request["data"] = request.pop("body")
return _sort_request_by_custom_order(request)
def _convert_jmespath(raw: Text) -> Text: def _convert_jmespath(raw: Text) -> Text:
if not isinstance(raw, Text): if not isinstance(raw, Text):
raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}") raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}")
@@ -153,6 +164,9 @@ def _ensure_step_attachment(step: Dict) -> Dict:
"name": step["name"], "name": step["name"],
} }
if "request" in step:
test_dict["request"] = _convert_request(step["request"])
if "variables" in step: if "variables" in step:
test_dict["variables"] = step["variables"] test_dict["variables"] = step["variables"]
@@ -181,11 +195,11 @@ def _ensure_step_attachment(step: Dict) -> Dict:
return test_dict return test_dict
def ensure_testcase_v3_api(api_content: Dict) -> Dict: def ensure_testcase_v4_api(api_content: Dict) -> Dict:
logger.info("convert api in v2 to testcase format v3") logger.info("convert api in v2/v3 to testcase format v4")
teststep = { teststep = {
"request": _sort_request_by_custom_order(api_content["request"]), "request": _convert_request(api_content["request"]),
} }
teststep.update(_ensure_step_attachment(api_content)) teststep.update(_ensure_step_attachment(api_content))
@@ -202,8 +216,8 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict:
} }
def ensure_testcase_v3(test_content: Dict) -> Dict: def ensure_testcase_v4(test_content: Dict) -> Dict:
logger.info("ensure compatibility with testcase format v2") logger.info("ensure compatibility with testcase format v2/v3")
v3_content = {"config": test_content["config"], "teststeps": []} v3_content = {"config": test_content["config"], "teststeps": []}
@@ -221,7 +235,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
teststep = {} teststep = {}
if "request" in step: if "request" in step:
teststep["request"] = _sort_request_by_custom_order(step.pop("request")) pass
elif "api" in step: elif "api" in step:
teststep["testcase"] = step.pop("api") teststep["testcase"] = step.pop("api")
elif "testcase" in step: elif "testcase" in step:
+51 -4
View File
@@ -26,6 +26,53 @@ class TestCompat(unittest.TestCase):
with self.assertRaises(exceptions.TestCaseFormatError): with self.assertRaises(exceptions.TestCaseFormatError):
compat.convert_variables(None, "examples/data/a-b.c/1.yml") compat.convert_variables(None, "examples/data/a-b.c/1.yml")
def test_convert_request(self):
request_with_json_body = {
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "application/json"
},
"body": {
"k1": "v1",
"k2": "v2"
}
}
self.assertEqual(
compat._convert_request(request_with_json_body),
{
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "application/json"
},
"json": {
"k1": "v1",
"k2": "v2"
}
}
)
request_with_text_body = {
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "text/plain"
},
"body": "have a nice day"
}
self.assertEqual(
compat._convert_request(request_with_text_body),
{
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "text/plain"
},
"data": "have a nice day"
}
)
def test_convert_jmespath(self): def test_convert_jmespath(self):
self.assertEqual(compat._convert_jmespath("content.abc"), "body.abc") self.assertEqual(compat._convert_jmespath("content.abc"), "body.abc")
self.assertEqual(compat._convert_jmespath("json.abc"), "body.abc") self.assertEqual(compat._convert_jmespath("json.abc"), "body.abc")
@@ -85,7 +132,7 @@ class TestCompat(unittest.TestCase):
[{"eq": ["body[0].name", 201]}], [{"eq": ["body[0].name", 201]}],
) )
def test_ensure_testcase_v3_api(self): def test_ensure_testcase_v4_api(self):
api_content = { api_content = {
"name": "get with params", "name": "get with params",
"request": { "request": {
@@ -98,7 +145,7 @@ class TestCompat(unittest.TestCase):
"validate": [{"eq": ["content.varB", 200]}, {"lt": ["json.0.varC", 0]}], "validate": [{"eq": ["content.varB", 200]}, {"lt": ["json.0.varC", 0]}],
} }
self.assertEqual( self.assertEqual(
compat.ensure_testcase_v3_api(api_content), compat.ensure_testcase_v4_api(api_content),
{ {
"config": { "config": {
"name": "get with params", "name": "get with params",
@@ -126,7 +173,7 @@ class TestCompat(unittest.TestCase):
}, },
) )
def test_ensure_testcase_v3(self): def test_ensure_testcase_v4(self):
testcase_content = { testcase_content = {
"config": {"name": "xxx", "base_url": "https://httpbin.org"}, "config": {"name": "xxx", "base_url": "https://httpbin.org"},
"teststeps": [ "teststeps": [
@@ -150,7 +197,7 @@ class TestCompat(unittest.TestCase):
], ],
} }
self.assertEqual( self.assertEqual(
compat.ensure_testcase_v3(testcase_content), compat.ensure_testcase_v4(testcase_content),
{ {
"config": {"name": "xxx", "base_url": "https://httpbin.org"}, "config": {"name": "xxx", "base_url": "https://httpbin.org"},
"teststeps": [ "teststeps": [
+8 -8
View File
@@ -11,8 +11,8 @@ from httprunner import __version__, exceptions
from httprunner.compat import ( from httprunner.compat import (
convert_variables, convert_variables,
ensure_path_sep, ensure_path_sep,
ensure_testcase_v3, ensure_testcase_v4,
ensure_testcase_v3_api, ensure_testcase_v4_api,
) )
from httprunner.loader import ( from httprunner.loader import (
convert_relative_project_root_dir, convert_relative_project_root_dir,
@@ -332,8 +332,8 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
"""convert valid testcase dict to pytest file path""" """convert valid testcase dict to pytest file path"""
# ensure compatibility with testcase format v2 # ensure compatibility with testcase format v2/v3
testcase = ensure_testcase_v3(testcase) testcase = ensure_testcase_v4(testcase)
# validate testcase format # validate testcase format
load_testcase(testcase) load_testcase(testcase)
@@ -373,9 +373,9 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
if not isinstance(test_content, Dict): if not isinstance(test_content, Dict):
raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}") raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}")
# api in v2 format, convert to v3 testcase # api in v2/v3 format, convert to v4 testcase
if "request" in test_content and "name" in test_content: if "request" in test_content and "name" in test_content:
test_content = ensure_testcase_v3_api(test_content) test_content = ensure_testcase_v4_api(test_content)
test_content.setdefault("config", {})["path"] = ref_testcase_path test_content.setdefault("config", {})["path"] = ref_testcase_path
ref_testcase_python_abs_path = make_testcase(test_content) ref_testcase_python_abs_path = make_testcase(test_content)
@@ -473,9 +473,9 @@ def __make(tests_path: Text):
) )
continue continue
# api in v2 format, convert to v3 testcase # api in v2/v3 format, convert to v4 testcase
if "request" in test_content and "name" in test_content: if "request" in test_content and "name" in test_content:
test_content = ensure_testcase_v3_api(test_content) test_content = ensure_testcase_v4_api(test_content)
if "config" not in test_content: if "config" not in test_content:
logger.warning( logger.warning(