tep完整教程幫你突破pytest

測試開發剛哥發表於2022-03-13

持續維護的教程

tep教程會隨著版本更新或經驗積累,持續維護在電子書中,最新的最全的內容請鎖定這篇文章【最新】tep完整教程幫你突破pytest

https://dongfanger.gitee.io/blog/chapters/tep.html

image-20220312205138970

對教程有任何疑問或建議,可新增微信交流喲:cekaigang

tep是個小工具

tepTry Easy Pytest的首字母縮寫,是一款基於pytest測試框架的測試工具,整合了各種實用的第三方包和優秀的自動化測試設計思想,幫你快速實現自動化專案落地。tep不是測試框架,只是一個小工具。在原理篇就能看出來,它所做的事情,就相當於膠水,把pytest相關的測試技術聚合在一起。假如您的公司想使用或推廣tep,那麼請不要說我們準備引入tep,而是應該說我們準備用pytest直接寫Python程式碼來實現自動化。tep只是幫你做到這一步的小小工具。

快速入門

安裝tep

pip install tep

image-20220312121613707

新建pytest專案

tep startproject demo

image-20220312100414996

啟動自帶FastAPI應用

執行utils/fastapi_mock.py指令碼。

image-20220312121339126

測試

執行samples資料夾下login_pay指令碼。

image-20220312122019200

生成報告

pytest samples/login_pay --tep-reports

image-20220312122403735

image-20220312122511029

使用篇

用例集

在tests目錄下將測試用例按功能模組分成多個用例集

tests
  user
    user_main_process.py
    user_validate.py
  teacher
    teacher_main_process.py
    teacher_validate.py
  student
    student_main_process.py
    student_validate.py

測試用例

用例的基本原則是用例解耦:每個.py檔案都是單獨的可執行的測試用例。

測試步驟

測試用例由測試步驟組成。步驟由描述、資料、請求、提取、斷言5個部分組成:

# 描述
# 資料
# 請求
response = request(
    "{method}",
    url="{url}",
    headers={headers},
    {body_grammar}
)
# 提取
# var = response.jmespath("expression")
# 斷言
assert response.status_code < 400

語法約定

強烈推薦直接編寫Python程式碼。無需額外學習新語法,精通Python語言和Python庫用法,讓你的程式碼能力直線上升。tep沒有做特殊封裝,只做了語法約定。tep編寫自動化指令碼的方法,是一種追求效率的極速寫法

介面管理

介面寫在用例步驟裡,不用單獨管理,不為了程式碼資料分離而分離。如果想單獨管理,可以參考示例程式碼中的mvc寫法,不推薦這種效率偏低的方式。

介面複用

介面複用的原則是邏輯相對簡單,url+入參+出參,比較固定且重複使用次數很多。符合複用要求的介面可以做成fixture,供測試用例使用。參考fixtures/fixture_login.py指令碼:

from tep.client import request
from tep.fixture import *


def _jwt_headers(token):
    return {"Content-Type": "application/json", "authorization": f"Bearer {token}"}


@pytest.fixture(scope="session")
def login(env_vars):
    # 封裝登入介面
    logger.info("Administrator login")
    response = request(
        "post",
        url=env_vars.domain + "/login",
        headers={"Content-Type": "application/json"},
        json={
            "username": "dongfanger",
            "password": "123456",
        }
    )
    assert response.status_code < 400
    response_token = jmespath.search("token", response.json())

    class Clazz:
        token = response_token
        jwt_headers = _jwt_headers(response_token)

    return Clazz

返回值使用類包了一層,一是為了在寫程式碼時會有語法智慧補全,二是方便後續擴充套件,直接給類新增新的屬性即可,不影響其他用例。

介面串聯

得益於一個.py檔案就是一條用例的約定。介面的串聯就能通過變數進行實現,從上個介面響應中取值,存入變數,放到下個介面的入參中,輕鬆完成。

全域性變數

env_vars是全域性變數池,提供了put()和get()方法對變數進行動態存取。在fixtures/fixture_env_vars.py可以設定預設變數

#!/usr/bin/python
# encoding=utf-8

from tep.dao import mysql_engine
from tep.fixture import *


@pytest.fixture(scope="session")
def env_vars(config):
    class Clazz(TepVars):
        env = config["env"]

        """變數定義開始"""
        # 環境變數
        mapping = {
            "qa": {  # qa環境
                "domain": "http://127.0.0.1:5000",  # 變數名:變數值
                "mysql_engine": mysql_engine("127.0.0.1",  # host
                                             "2306",  # port
                                             "root",  # username
                                             "123456",  # password
                                             "qa"),  # dbname
            },
            "release": {  # release環境
                "domain": "https://release.com",  # 變數名:變數值
                "mysql_engine": mysql_engine("127.0.0.1",
                                             "2306",
                                             "root",
                                             "123456",
                                             "release"),
            }
            # 繼續新增
        }
        # 定義類屬性,敲程式碼時會自動補全
        domain = mapping[env]["domain"]
        mysql_engine = mapping[env]["mysql_engine"]
        """變數定義結束"""

    return Clazz()

區域性變數

就像正常的Python變數一樣使用,沒有特殊的語法。

環境切換

conf.yaml中可以切換執行環境:

env: qa

環境之間的差別體現在環境變數,環境變數也是在fixtures/fixture_env_vars.py中進行預設的。預設有qarelease2個環境。

資料驅動

推薦使用pytest.mark.parametrize

//TODO整合對excel、json、yaml檔案讀寫方法。

斷言

採用Python原生的assert斷言

//TODO整理assert用法。

測試報告

在pytest命令列新增引數--tep-reports就能一鍵生成Allure測試報告,並且會把請求入參和響應出參,記錄在測試報告中。

pytest --tep-reports

自定義日誌

編輯utils/http_client.py對日誌進行自定義,用例中引用新版本request

from utils.http_client import request

參考示例samples/http/test_request_monkey_patch.py

Pairwise演算法生成功能用例

Pairwise演算法能針對多條件組合用例,從笛卡爾積中,根據兩兩組合過濾,生成更為精簡的測試用例。

輸入3個條件:

  • 'M', 'O', 'P'
  • 'W', 'L', 'I'
  • 'C', 'E'
from tep.func import pairwise

def test_pairwise():
    enum = [['M', 'O', 'P'], ['W', 'L', 'I'], ['C', 'E']]
    result = pairwise(enum)
    print(f"\npair total:{len(result)}")
    for p in result:
        print(p)

笛卡爾積有18種組合,經過Pairwise演算法過濾後,只會保留9組用例:

cartesian product total:18
 100% [■■■■■■■■■■]
pair total:9
('M', 'W', 'E')
('M', 'L', 'E')
('M', 'I', 'C')
('O', 'W', 'E')
('O', 'L', 'E')
('O', 'I', 'C')
('P', 'W', 'C')
('P', 'L', 'C')
('P', 'I', 'E')

錄製流量生成自動化用例

①手動設定系統代理。

②命令列cd到utils目錄下,在mitm.py中設定過濾域名

mitmdump -s mitm.py開始錄製。

用例會自動生成到tests/mitm資料夾下。

原理篇

程式碼是最好的文件:

https://github.com/dongfanger/tep

//TODO完善程式碼註釋

image-20220313145244920

pypi庫

tep可以通過pip直接安裝,這是因為原始碼上傳到了pypi官方庫。上傳藉助了poetry來實現:

poetry install --no-dev
poetry build
poetry publish

執行這3條命令,然後輸入pypi註冊的使用者名稱和密碼即可。

整合第三方包

poetry包管理器可以通過命令安裝包:

poetry install package
poetry remove package

整合以後的包會隨著tep一起安裝。

專案腳手架

tep能從系統命令列來呼叫,也是藉助poetry來實現的:

#  pyproject.toml
[tool.poetry.scripts]
tep = "tep.cli:main"

這相當於註冊了系統命令,呼叫後會執行tep.cli:main函式:

import argparse
import sys

from tep import __description__, __version__
from tep.scaffold import init_parser_scaffold, main_scaffold


def main():
    """Parse command line options and run commands.
    """
    parser = argparse.ArgumentParser(description=__description__)
    parser.add_argument(
        "-V", "--version", dest="version", action="store_true", help="show version"
    )
    subparsers = parser.add_subparsers(help="sub-command help")
    sub_parser_scaffold = init_parser_scaffold(subparsers)

    if len(sys.argv) == 1:
        # tep
        parser.print_help()
        sys.exit(0)
    elif len(sys.argv) == 2:
        # print help for sub-commands
        if sys.argv[1] in ["-V", "--version"]:
            # tep -V
            print(f"{__version__}")
        elif sys.argv[1] in ["-h", "--help"]:
            # tep -h
            parser.print_help()
        elif sys.argv[1] == "startproject":
            # tep startproject
            sub_parser_scaffold.print_help()
        sys.exit(0)

    args = parser.parse_args()

    if args.version:
        print(f"{__version__}")
        sys.exit(0)

    if sys.argv[1] == "startproject":
        main_scaffold(args)

startproject會呼叫main_scaffold函式,這裡面的邏輯很簡單,就是建立資料夾和檔案,檔案內容是已經寫好的樣板程式碼。

變數池

變數池是在tep/fixture.py中實現的:

class TepVars:
    def __init__(self):
        self.vars_ = {}

    def put(self, key, value):
        self.vars_[key] = value

    def get(self, key):
        value = ""
        try:
            value = self.vars_[key]
        except KeyError:
            logger.error(f"env_vars doesnt have this key: {key}")
        return value

它就是一個具有get和put方法的類,變數存在self.vars_這個全域性字典中,所有指令碼共享同一個變數池。

環境變數

環境配置是通過config來讀取的:

@pytest.fixture(scope="session")
def config():
    config_path = os.path.join(Project.dir, "conf.yaml")
    with open(config_path, "r", encoding="utf-8") as f:
        conf = yaml.load(f.read(), Loader=yaml.FullLoader)
        return conf

它是個fixture,會在fixtures/fixture_env_vars.py中引用到:

@pytest.fixture(scope="session")
def env_vars(config):
    class Clazz(TepVars):
        env = config["env"]

這樣就能設定環境變數了。

fixture自動匯入

在conftest.py中,進行了fixture自動匯入:

# 自動匯入fixtures
_fixtures_dir = os.path.join(_project_dir, "fixtures")
_fixtures_paths = []
for root, _, files in os.walk(_fixtures_dir):
    for file in files:
        if file.startswith("fixture_") and file.endswith(".py"):
            full_path = os.path.join(root, file)
            import_path = full_path.replace(_fixtures_dir, "").replace("\\", ".").replace("/", ".").replace(".py", "")
            _fixtures_paths.append("fixtures" + import_path)
pytest_plugins = _fixtures_paths

它會掃描fixtures目錄下所有以fixture_開頭和.py結尾的檔案,然後以pytest_plugins形式新增到執行環境中。

requests猴子補丁

requests藉助於裝飾器打了猴子補丁,tep/client.py

def tep_request_monkey_patch(req, *args, **kwargs):
    start = time.process_time()
    response = req(*args, **kwargs)
    end = time.process_time()
    elapsed = str(decimal.Decimal("%.3f" % float(end - start))) + "s"
    log4a = "{}{} status:{}  response:{}  elapsed:{}"
    try:
        kv = ""
        for k, v in kwargs.items():
            # if not json, str()
            try:
                v = json.dumps(v, ensure_ascii=False)
            except TypeError:
                v = str(v)
            kv += f" {k}:{v} "
        args = list(args)
        args += ["", ""]
        method, url, *t = args
        method_url = ""
        if method:
            method_url = f'method:"{method}" '
        if url:
            method_url += f'\nurl:"{url}" '
        request_response = log4a.format(method_url, kv, response.status_code, response.text, elapsed)
        logger.info(request_response)
        allure.attach(request_response, f'request & response', allure.attachment_type.TEXT)
    except AttributeError:
        logger.error("request failed")
    except TypeError:
        logger.warning(log4a)
    return TepResponse(response)


def request_wrapper(req):
    def send(*args, **kwargs):
        return tep_request_monkey_patch(req, *args, **kwargs)

    return send


@request_wrapper
def request(method, url, **kwargs):
    # 這是reqeusts原生方法

沒有對requests做任何改動,只加了日誌和報告內容。

一鍵生成Allure測試報告

--tep-reports是通過pytest plugin來實現的:

#!/usr/bin/python
# encoding=utf-8

import os
import shutil
import tempfile

import allure_commons
from allure_commons.logger import AllureFileLogger
from allure_pytest.listener import AllureListener
from allure_pytest.plugin import cleanup_factory

from tep.fixture import Project
from tep.func import current_time

allure_temp = tempfile.mkdtemp()


class Plugin:
    @staticmethod
    def pytest_addoption(parser):
        parser.addoption(
            "--tep-reports",
            action="store_const",
            const=True,
            help="Create tep allure HTML reports."
        )

    @staticmethod
    def _tep_reports(config):
        if config.getoption("--tep-reports") and not config.getoption("allure_report_dir"):
            return True
        else:
            return False

    @staticmethod
    def pytest_configure(config):
        if Plugin._tep_reports(config):
            test_listener = AllureListener(config)
            config.pluginmanager.register(test_listener)
            allure_commons.plugin_manager.register(test_listener)
            config.add_cleanup(cleanup_factory(test_listener))

            clean = config.option.clean_alluredir
            file_logger = AllureFileLogger(allure_temp, clean)
            allure_commons.plugin_manager.register(file_logger)
            config.add_cleanup(cleanup_factory(file_logger))

    @staticmethod
    def pytest_sessionfinish(session):
        if Plugin._tep_reports(session.config):
            reports_dir = os.path.join(Project.dir, "reports")
            new_report = os.path.join(reports_dir, "report-" + current_time().replace(":", "-").replace(" ", "-"))
            if os.path.exists(reports_dir):
                his_reports = os.listdir(reports_dir)
                if his_reports:
                    latest_report_history = os.path.join(reports_dir, his_reports[-1], "history")
                    shutil.copytree(latest_report_history, os.path.join(allure_temp, "history"))
            os.system(f"allure generate {allure_temp} -o {new_report}  --clean")
            shutil.rmtree(allure_temp)

通過pytest_sessionfinish鉤子函式,在pytest執行結束時,生成測試報告。同時會把歷史資料保留下來,以在Allure報告的趨勢圖中進行展示。

//TODO其他原理慢慢更新,歡迎提出疑問,不斷補充。

附錄

tep相比於pytest優勢

【專案建立】

專案腳手架快速建立自動化專案;

良好的專案結構設計;

【上手簡單】

遵循Python原生語法,沒有額外負擔;

提供豐富的介面自動化實踐示例;

【優雅整合】

保留requests庫用法,採用猴子補丁動態輸出日誌;

pytest命令列引數一鍵生成Allure測試報告;

【平臺支援】

teprunner測試平臺線上管理pytest指令碼;

支援Git一鍵同步至平臺;

tep測試平臺化思路

teprunner是基於tep的測試平臺

從測試工具轉變到測試平臺,最重要是要想清楚用例的執行流程。從前端錄入用例資訊後,通過後端儲存到資料庫,再把資料組裝出來,變成可執行的檔案。teprunner的做法是,把pytest作為引擎,用例全部轉化為檔案,然後使用pytest命令執行用例。

用例解耦是實現平臺化的關鍵原則。tep是按照一個.py檔案一條用例的約定來編寫指令碼的,使得每個檔案都是獨立的可執行的。這就能很好的對應到,前端測試用例的增刪改查。假如用例沒有解耦,Python檔案之間存在非常多的依賴,那麼想做成Web平臺是很困難的,在介面上根本無法操作。

專案腳手架為平臺化提供了非常大的便利。在測試平臺建立專案時,就會呼叫tep startproject建立一個專案腳手架,相當於給指令碼執行初始化了一套隔離的執行環境,專案的用例之間互不干擾。

至於fixtures、環境變數等功能,如果做好了分層設計,這些都是水到渠成的事了。在做平臺之前,只是為了多人協作方便,把conftest裡面的fixture抽了出來,但是在平臺化時,抽出來的fixtures正好可以做成一個單獨的功能點。環境變數也是在做平臺之前,只是想用yaml來管理配置,但是在平臺化時,正好可以用來在前端切換環境,結合fixture_env_vars.py做成環境變數的功能。

找準測試平臺定位才能遊刃有餘。測試平臺只是一個殼子,做成什麼樣的平臺,取決於對平臺的定位,以及技術實現的能力。正是因為沒有大牛的技術,無法做成大而全的測試平臺,teprunner測試平臺的才定位於pytest指令碼線上管理平臺。這對於tep來說,恰好是剛剛好的選擇。

相關文章