接口自動(dòng)化框架2-升級(jí)版(Pytest+request+Allure)

前言:

接口自動(dòng)化是指模擬程序接口層面的自動(dòng)化,由于接口不易變更,維護(hù)成本更小,所以深受各大公司的喜愛(ài)。
第一版入口:接口自動(dòng)化框架(Pytest+request+Allure)
本次版本做了一些升級(jí),增加了自動(dòng)生成testcase等,一起來(lái)看看吧!~~


一、簡(jiǎn)單介紹

環(huán)境:Mac+Python 3+Pytest+Allure+Request

pytest==3.6.0
pytest-allure-adaptor==1.7.10
pytest-rerunfailures==5.0
allure-python-commons==2.7.0
configparser==3.5.0
PyYAML==3.12
requests==2.18.4
simplejson==3.16.0
ruamel.yaml==0.18.16

流程:Charles導(dǎo)出接口數(shù)據(jù)-自動(dòng)生成測(cè)試用例-修改測(cè)試用例-執(zhí)行測(cè)試用例-生成Allure報(bào)告
開(kāi)源: 點(diǎn)擊這里,跳轉(zhuǎn)到github
備注??:Charles導(dǎo)出接口應(yīng)選擇文件類型為JSON Session File(.chlsj)

重要模塊介紹:
1、writeCase.py :自動(dòng)讀取新的Charles文件,并自動(dòng)生成測(cè)試用例
2、apiMethod.py:封裝request方法,可以支持多協(xié)議擴(kuò)展(get\post\put)
3、checkResult.py:封裝驗(yàn)證response方法
4、setupMain.py: 核心代碼,定義并執(zhí)行用例集,生成報(bào)告

??2020-5-29更新:
pytest-allure-adaptor已棄用,更換為allure-pytest
pip uninstall pytest-allure-adaptor
pip install allure-pytest
代碼已更新。 點(diǎn)擊這里,跳轉(zhuǎn)到github

__

二、目錄介紹

Aff_service.png

三、代碼分析

1、測(cè)試數(shù)據(jù)yml(自動(dòng)生成的yml文件)

# 用例基本信息
test_info:
      # 用例標(biāo)題,在報(bào)告中作為一級(jí)目錄顯示
      title: blogpost
      # 用例ID
      id: test_reco_01
      # 請(qǐng)求的域名,可寫(xiě)死,也可寫(xiě)成模板關(guān)聯(lián)host配置文件
      host: ${host}$
      # 請(qǐng)求地址 選填(此處不填,每條用例必填)
      address: /api/v2/recomm/blogpost/reco

# 前置條件,case之前需關(guān)聯(lián)的接口
premise:

# 測(cè)試用例
test_case:
    - test_name: reco_1
      # 第一條case,info可不填
      info: reco
      # 請(qǐng)求協(xié)議
      http_type: https
      # 請(qǐng)求類型
      request_type: POST
      # 參數(shù)類型
      parameter_type: application/json
      # 請(qǐng)求地址
      address: /api/v2/recomm/blogpost/reco
      # 請(qǐng)求頭
      headers:
      # parameter為文件路徑時(shí)
      parameter: reco.json
      # 是否需要獲取cookie
      cookies: False
      # 是否為上傳文件的接口
      file: false
      # 超時(shí)時(shí)間
      timeout: 20

      # 校驗(yàn)列表  list or dict
      # 不校驗(yàn)時(shí) expected_code, expected_request 均可不填
      check:
        expected_request: result_reco.json
        check_type: only_check_status
        expected_code: 503

      # 關(guān)聯(lián)鍵
      relevance:

2、測(cè)試case(自動(dòng)生成的case.py)

@allure.feature(case_dict["test_info"]["title"])
class TestReco:

    @pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[])
    @allure.story("reco")
    @pytest.mark.flaky(reruns=3, reruns_delay=3)
    def test_reco(self, case_data):
        """

        :param case_data: 測(cè)試用例
        :return:
        """
        self.init_relevance = ini_request(case_dict, PATH)
        # 發(fā)送測(cè)試請(qǐng)求
        api_send_check(case_data, case_dict, self.init_relevance, PATH)

3、writeCase.py (封裝方法:自動(dòng)生成測(cè)試case)

def write_case(_path):
    yml_list = write_case_yml(_path)
    project_path = str(os.path.abspath('.').split('/bin')[0])
    test_path = project_path+'/aff/testcase/'
    src = test_path+'Template.py'

    for case in yml_list:
        yml_path = case.split('/')[0]
        yml_name = case.split('/')[1]
        case_name = 'test_' + yml_name + '.py'
        new_case = test_path + yml_path + '/' + case_name
        mk_dir(test_path + yml_path)
        if case_name in os.listdir(test_path + yml_path):
            pass
        else:
            shutil.copyfile(src, new_case)
            with open(new_case, 'r') as fw:
                source = fw.readlines()
            n = 0
            with open(new_case, 'w') as f:
                for line in source:
                    if 'PATH = setupMain.PATH' in line:
                        line = line.replace("/aff/page/offer", "/aff/page/%s" % yml_path)
                        f.write(line)
                        n = n+1
                    elif 'case_dict = ini_case' in line:
                        line = line.replace("Template", yml_name)
                        f.write(line)
                        n = n + 1
                    elif 'class TestTemplate' in line:
                        line = line.replace("TestTemplate", "Test%s" % yml_name.title().replace("_", ""))
                        f.write(line)
                        n = n + 1
                    elif '@allure.story' in line:
                        line = line.replace("Template", yml_name)
                        f.write(line)
                        n = n + 1
                    elif 'def test_template' in line:
                        line = line.replace("template", yml_name.lower())
                        f.write(line)
                        n = n + 1

                    else:
                        f.write(line)
                        n += 1
                for i in range(n, len(source)):
                    f.write(source[i])

4、apiMethod.py(封裝方法:http多協(xié)議)

def post(header, address, request_parameter_type, timeout=8, data=None, files=None):
    """
    post請(qǐng)求
    :param header: 請(qǐng)求頭
    :param address: 請(qǐng)求地址
    :param request_parameter_type: 請(qǐng)求參數(shù)格式(form_data,raw)
    :param timeout: 超時(shí)時(shí)間
    :param data: 請(qǐng)求參數(shù)
    :param files: 文件路徑
    :return:
    """
    if 'form_data' in request_parameter_type:
        for i in files:
            value = files[i]
            if '/' in value:
                file_parm = i
                files[file_parm] = (os.path.basename(value), open(value, 'rb'))
        enc = MultipartEncoder(
            fields=files,
            boundary='--------------' + str(random.randint(1e28, 1e29 - 1))
        )
        header['Content-Type'] = enc.content_type

        response = requests.post(url=address, data=enc, headers=header, timeout=timeout)
    else:
        response = requests.post(url=address, data=data, headers=header, timeout=timeout, files=files)
    try:
        if response.status_code != 200:
            return response.status_code, response.text
        else:
            return response.status_code, response.json()
    except json.decoder.JSONDecodeError:
        return response.status_code, ''
    except simplejson.errors.JSONDecodeError:
        return response.status_code, ''
    except Exception as e:
        logging.exception('ERROR')
        logging.error(e)
        raise

5、checkResult.py(封裝方法:校驗(yàn)response結(jié)果)

def check_result(test_name, case, code, data, _path, relevance=None):
    """
    校驗(yàn)測(cè)試結(jié)果
    :param test_name: 測(cè)試名稱
    :param case: 測(cè)試用例
    :param code: HTTP狀態(tài)
    :param data: 返回的接口json數(shù)據(jù)
    :param relevance: 關(guān)聯(lián)值對(duì)象
    :param _path: case路徑
    :return:
    """
    # 不校驗(yàn)結(jié)果
    if case["check_type"] == 'no_check':
        with allure.step("不校驗(yàn)結(jié)果"):
            pass
    # json格式校驗(yàn)
    elif case["check_type"] == 'json':
        expected_request = case["expected_request"]
        if isinstance(case["expected_request"], str):
            expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
        with allure.step("JSON格式校驗(yàn)"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach('期望data', str(expected_request))
            allure.attach("實(shí)際code", str(code))
            allure.attach('實(shí)際data', str(data))
        if int(code) == case["expected_code"]:
            if not data:
                data = "{}"
            check_json(expected_request, data)
        else:
            raise Exception("http狀態(tài)碼錯(cuò)誤!\n %s != %s" % (code, case["expected_code"]))
    # 只校驗(yàn)狀態(tài)碼
    elif case["check_type"] == 'only_check_status':
        with allure.step("校驗(yàn)HTTP狀態(tài)"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach("實(shí)際code", str(code))
            allure.attach('實(shí)際data', str(data))
        if int(code) == case["expected_code"]:
            pass
        else:
            raise Exception("http狀態(tài)碼錯(cuò)誤!\n %s != %s" % (code, case["expected_code"]))
    # 完全校驗(yàn)
    elif case["check_type"] == 'entirely_check':
        expected_request = case["expected_request"]
        if isinstance(case["expected_request"], str):
            expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
        with allure.step("完全校驗(yàn)"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach('期望data', str(expected_request))
            allure.attach("實(shí)際code", str(code))
            allure.attach('實(shí)際data', str(data))
        if int(code) == case["expected_code"]:
            result = operator.eq(expected_request, data)
            if result:
                pass
            else:
                raise Exception("完全校驗(yàn)失?。?%s ! = %s" % (expected_request, data))
        else:
            raise Exception("http狀態(tài)碼錯(cuò)誤!\n %s != %s" % (code, case["expected_code"]))
    # 正則校驗(yàn)
    elif case["check_type"] == 'Regular_check':
        if int(code) == case["expected_code"]:
            try:
                result = ""
                if isinstance(case["expected_request"], list):
                    for i in case[""]:
                        result = re.findall(i.replace("\"","\""), str(data))
                        allure.attach('校驗(yàn)完成結(jié)果\n',str(result))
                else:
                    result = re.findall(case["expected_request"].replace("\"", "\'"), str(data))
                    with allure.step("正則校驗(yàn)"):
                        allure.attach("期望code", str(case["expected_code"]))
                        allure.attach('正則表達(dá)式', str(case["expected_request"]).replace("\'", "\""))
                        allure.attach("實(shí)際code", str(code))
                        allure.attach('實(shí)際data', str(data))
                        allure.attach(case["expected_request"].replace("\"", "\'") + '校驗(yàn)完成結(jié)果',
                                      str(result).replace("\'", "\""))
                if not result:
                    raise Exception("正則未校驗(yàn)到內(nèi)容! %s" % case["expected_request"])
            except KeyError:
                raise Exception("正則校驗(yàn)執(zhí)行失?。?%s\n正則表達(dá)式為空時(shí)" % case["expected_request"])
        else:
            raise Exception("http狀態(tài)碼錯(cuò)誤!\n %s != %s" % (code, case["expected_code"]))

    else:
        raise Exception("無(wú)該校驗(yàn)方式%s" % case["check_type"])

6、setupMain.py(執(zhí)行用例集,生成測(cè)試報(bào)告)

def invoke(md):
    output, errors = subprocess.Popen(md, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
    o = output.decode("utf-8")
    return o


if __name__ == '__main__':
    LogConfig(PATH)
    write_case(har_path)
    args = ['-s', '-q', '--alluredir', xml_report_path]
    pytest.main(args)
    cmd = 'allure generate %s -o %s' % (xml_report_path, html_report_path)
    invoke(cmd)

7、測(cè)試報(bào)告
Allure報(bào)告.png

以上,喜歡的話請(qǐng)點(diǎn)贊??吧~
歡迎關(guān)注我的簡(jiǎn)書(shū),博客,TesterHome,Github~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容