The strongest automation framework, Pytest automated testing - dynamic switching environment combat (super fine finishing)


foreword

Before we start, we can think about a few questions: What is the purpose of dynamically switching the test environment (what problems can it solve)? How to achieve (implementation plan)? What are the specific steps (implementation process)?

1. What is the purpose of dynamically switching the test environment?

What is the purpose of dynamically switching the test environment, or what kind of problems it can solve: it
is convenient to quickly verify the performance of functions in different environments.

For example: Some functions (interfaces behind them) are normal in the development environment, but there are problems in the test or pre-release environment, which is convenient for quickly verifying the performance of each function in different environments;

Save the cumbersome steps of modifying configuration parameters. Usually, our configuration information is written in the configuration file, and then the test case reads different configuration information in the configuration file. If you want to switch the environment, you need to modify the configuration file or read the logic of the configuration.

The dynamic switching test environment can automatically identify, parse and return the corresponding data according to the command line parameters we pass in and the prefabricated reading configuration strategy.

Empowering the testing framework. The test framework must be able to run a set of scripts in multiple environments, support environment switching, and be able to perform automated configuration (including system configuration, test data configuration, etc.) according to the environment.

In fact, the above is summarized as follows: a set of test scripts can be automatically configured according to the environment, eliminating the need to manually configure parameters, and can run in multiple environments, thereby quickly verifying the performance of each interface and related services in different environments.

2. How to dynamically switch the test environment?

Implementation plan:
We hope that there can be a switch to freely control the execution environment of the script, instead of requiring us to modify it manually.

For example: when dev is selected, the configuration and test data of the development environment are automatically read: url, database configuration, account password, and test data;
when switching to test, the configuration and test data of the test environment are automatically read.

The general implementation principle is as follows:

C1

The user passes in parameters through the pytest command line to drive script execution (pytest_addoption is used to implement custom command line parameters);

The fixture function get_env is used to obtain the command line parameters input by the user and pass them to each fixture function in fixture.py;

Each fixture function in fixture.py analyzes the content of the data file corresponding to the test environment according to the environment parameter value provided by get_env: URL (get_url), account number (get_user), database configuration (get_db), and passes it to the api class (api_module_A...B...C), login method (login), database connection method (use_db), etc. for instantiation operations. This part of the fixture function is then passed to the test case for pre- and post-use operations (equivalent to setup/teardown) ;

Finally, the test case calls the api function of each module according to the instance object and configuration information returned by each fixture function, executes the test, and reads and writes the database to realize data verification and assertion, so as to finally realize the switching environment strategy;

3. Directory structure & framework design tips

Directory structure:
The project structure is roughly as follows. As for the directory structure and file naming, it can only be said that radish and green vegetables have their own preferences. For example, some people like to name the common directory for storing public methods as utils, and the api directory for storing various api modules as src...

C2

Tips for designing the automated testing framework:
api: store APIs that encapsulate various projects and modules, such as the jk project payment module, which can be named jk_pay.py
;

common: store public methods, such as based on the http protocol requests library, you can name it http_requests.py; through the file name, you can know the function of this file with a high probability, for example, you can directly know that it is to parse the excel file through the name of parse_excel;

main: the main entry of the framework, which stores the files used to execute use cases in batches, such as: run_testcase_by_tag.py (provided that the use cases are all tagged), run_testcase_by_name.py; fixture: stores fixture files, and it is recommended that each project has a fixture file that does not affect each other, such as: jk_fixture.py, jc_fixture.py; test_case: stores test case
files
;

conftest.py: stores some hook functions and global fixture functions, such as the function pytest_addoption for custom command line parameters mentioned above, and the fixture function get_env for obtaining command line parameters; pytest.ini: pytest framework configuration file
;

4. Implementation process

The above-mentioned solution may be difficult to understand from the text level alone. Let’s describe the implementation process in detail in combination with specific code cases.

Implement a custom command line parameter tool:
define a hook function in conftest.py to implement a custom command line tool named pytest_addoption (fixed writing method), which is used to pass in different environment parameters in the command line;

def pytest_addoption(parser):
    """
    添加命令行参数
    parser.addoption为固定写法
    default 设置一个默认值,此处设置默认值为test
    choices 参数范围,传入其他值无效
    help 帮助信息
    """
    parser.addoption(
        "--env", default="test", choices=["dev", "test", "pre"], help="enviroment parameter"
    )

Define the fixture function for obtaining command line parameters:
define the fixture function of get_env in conftest.py, which is used to obtain the parameter value entered by the user on the command line, and pass it to each fixture function in fixture.py. pytestconfig is a shortcut to request.config, so request.config can also be written as pytestconfig.

@pytest.fixture(scope="session")
def get_env(request):
    return request.config.getoption("--env")

Let's test whether the command line can input parameters and whether the fixture function get_env can be obtained. We can simply define a test case:

def test_env(get_env):
    print(f"The current environment is: {
      
      get_env}")

Then execute this test case via the command line:

pytest -s -v --env dev test_env.py::test_env

The execution results are as follows:

C3

Define the environment analysis strategy:
For example, if the current project is a jc project, you can define a jc_fixture.py file in the fixture directory to store the fixture functions related to this project.

Each fixture function in fixture.py analyzes the content of the data file corresponding to the test environment according to the environment parameter value provided by get_env: URL (get_url), account number (get_user), database configuration (get_db), and passes it to the api class (api_module_A...B...C) for instantiation, login method (login), database connection method (use_db), etc., for initialization. );

import pytest
from config.config import URLConf, PasswordConf, UsernameConf, ProductIDConf
from api.jc_common import JCCommon
from api.jc_resource import JCResource
from config.db_config import DBConfig
from common.mysql_handler import MySQL

@pytest.fixture(scope="session")
def get_url(get_env):
    """解析URL"""
    global url
    if get_env == "test":
        print("当前环境为测试环境")
        url = URLConf.RS_TEST_URL.value
    elif get_env == "dev":
        print("当前环境为开发环境")
        url = URLConf.RS_DEV_URL.value
    elif get_env == "pre":
        print("当前环境为预发布环境")
        url = URLConf.RS_PRE_URL.value
    return url

@pytest.fixture(scope="session")
def get_user(get_env):
    """解析登录用户"""
    global username_admin, username_boss
    # 若get_env获取到的是test,则读取配置文件中测试环境的用户名
        if get_env == "test":
        username_admin = UsernameConf.RS_TEST_ADMIN.value
        username_boss = UsernameConf.RS_TEST_BOSS.value
      # 若get_env获取到的是dev,则读取配置文件中开发环境的用户名
    elif get_env == "dev":
        username_admin = UsernameConf.RS_TEST_ADMIN.value
        username_boss = UsernameConf.RS_TEST_BOSS.value
      # 若get_env获取到的是pre,则读取配置文件中预发布环境的用户名
    elif get_env == "pre":
        username_admin = UsernameConf.RS_TEST_ADMIN.value
        username_boss = UsernameConf.RS_TEST_BOSS.value

@pytest.fixture(scope="session")
def get_db(get_env):
    """解析数据库配置"""
    global db_host, db_pwd, db_ssh_host, db_ssh_pwd, db_name
    if get_env == "test":
        db_host = DBConfig.db_test.get('host')
        db_pwd = DBConfig.db_test.get('pwd')
        db_ssh_host = DBConfig.db_test.get('ssh_host')
        db_ssh_pwd = DBConfig.db_test.get('ssh_pwd')
        db_name = DBConfig.db_test.get('dbname_jc')
    elif get_env == "dev":
        db_host = DBConfig.db_test.get('host')
        db_pwd = DBConfig.db_test.get('pwd')
        db_ssh_host = DBConfig.db_test.get('ssh_host')
        db_ssh_pwd = DBConfig.db_test.get('ssh_pwd')
        db_name = DBConfig.db_test.get('dbname_jc')
    elif get_env == "pre":
        db_host = DBConfig.db_test.get('host')
        db_pwd = DBConfig.db_test.get('pwd')
        db_ssh_host = DBConfig.db_test.get('ssh_host')
        db_ssh_pwd = DBConfig.db_test.get('ssh_pwd')
        db_name = DBConfig.db_test.get('dbname_jc')

@pytest.fixture(scope="session")
def jc_common(get_env, get_url):
    """传入解析到的URL、实例化jc项目公共接口类"""
    product_id = ProductIDConf.JC_PRODUCT_ID.value
    jc_common = JCCommon(product_id=product_id, url=get_url)
    return jc_common

@pytest.fixture(scope="session")
def jc_resource(get_env, get_url):
    """传入解析到的URL、实例化jc项目测试接口类"""
    product_id = ProductIDConf.JC_PRODUCT_ID.value
    jc_resource = JCResource(product_id=product_id, url=get_url)
    return jc_resource

@pytest.fixture(scope="class")
def rs_admin_login(get_user, jc_common):
    """登录的fixture函数"""
    password = PasswordConf.PASSWORD_MD5.value
    login = jc_common.login(username=username_shipper, password=password)
    admin_user_id = login["b"]
    return admin_user_id

@pytest.fixture(scope="class")
def jc_get_admin_user_info(jc_common, jc_admin_login):
    """获取用户信息的fixture函数"""
    user_info = jc_common.get_user_info(user_id=rs_shipper_login)
    admin_cpy_id = user_info["d"]["b"]
    return admin_cpy_id

@pytest.fixture(scope="class")
def use_db(get_db):
    """链接数据库的fixture函数"""
    mysql = MySQL(host=db_host, pwd=db_pwd, ssh_host=db_ssh_host, ssh_pwd=db_ssh_pwd, dbname=db_name)
    yield mysql
    mysql.disconnect()

Test case reference fixture:
encapsulate the api function of each module to be tested
Login module: jc_common.py

from common.http_requests import HttpRequests


class JcCommon(HttpRequests):
    def __init__(self, url, product_id):
        super(JcCommon, self).__init__(url)
        self.product_id = product_id

    def login(self, username, password):
        '''用户登录'''
        headers = {
    
    "product_id": str(self.product_id)}
        params = {
    
    "a": int(username), "b": str(password)}
        response = self.post(uri="/userlogin", headers=headers, params=params)
        return response

    def get_user_info(self, uid, token):
        '''获取用户信息'''
        headers = {
    
    "user_id": str(uid), "product_id": str(self.product_id), "token": token}
        response = self.post(uri="/user/login/info", headers=headers)
        return response

Business module: jc_resource.py

import random
from common.http_requests import HttpRequests
from faker import Faker


class RSResource(HttpRequests):
    def __init__(self, url, product_id):
        super(RSResource, self).__init__(url)
        self.product_id = product_id
        self.faker = Faker(locale="zh_CN")

    def add_goods(self, cpy_id, user_id, goods_name, goos_desc='', goods_type='', goos_price=''):
        """新增商品"""
        headers = {
    
    "product_id": str(self.product_id), "cpy_id": str(cpy_id), "user_id": str(user_id)}
        params = {
    
    "a": goods_name, "b": goos_desc, "c": goods_type, "d": goos_price}
        r = self.post(uri="/add/goods", params=params, headers=headers)
        return r

    def modify_goods(self, cpy_id, user_id, goods_name, goos_desc='', goods_type='', goos_price=''):
        """修改商品信息"""
        headers = {
    
    "product_id": str(self.product_id), "cpy_id": str(cpy_id), "user_id": str(user_id)}
        params = {
    
    "a": car_name, "ab": car_id, "b": company_id, "c": car_or_gua}
        r = self.post(uri="/risun/res/car/add/blacklist?md=065&cmd=006", params=params, headers=headers)
        return r

The api functions of each module exist independently, which isolates the configuration from the function, and does not involve any fixture references. In this way, no matter how the test URL, user name, and database are changed, there is no need to modify the api function of the module to be tested. Basically, it can be done once and for all, unless the interface address and parameters are changed.

Test case:
The test case class TestJcSmoke of the JC project calls the api function of each business module according to the instance object and configuration information returned by each fixture function in each jc_fixture.py, executes the test, and reads and writes the database to realize data verification and assertion;

import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
import allure
from fixture.jc_fixture import *
from common.parse_excel import ParseExcel

logger = LogGen("JC接口Smoke测试").getLog()

@allure.feature("JC项目接口冒烟测试")
class TestJcSmoke:
    def setup_class(self):
        self.fake = Faker("zh_CN")

    # 将fixture中的jc_resource实例、数据库实例、登录等fixture函数传递给测试用例进行调用
    @pytest.mark.jc_smoke
    @allure.story("商品管理")
    def test_01_goods_flow(self, jc_resource, jc_admin_login, jc_get_admin_user_info, use_db):
        """测试商品增删改查接口"""
        user_id = jc_admin_login
        cpy_id = jc_get_admin_user_info
        goods_name = "iphone 14pro max 512G"
        try:
            logger.info(f"新增'{
      
      goods_name}'商品")
            with allure.step("调用添加商品接口"):
                add_goods = jc_resource.add_goods(cpy_id, user_id, goods_name, goods_type=1)
                assert add_goods["a"] == 200
                self.goods_id = add_goods["d"]
                select_db = use_db.execute_sql(
                    f"SELECT * FROM goods_info WHERE company_id = {
      
      cpy_id} AND id = {
      
      self.goods_id}")  # 查询数据库是否存在新增的数据
                assert goods_name in str(select_db)
                logger.info(f"商品'{
      
      goods_name}'新增成功")

            logger.info(f"修改'{
      
      goods_name}'的商品信息")
            with allure.step("调用修改商品接口"):
                modify_goods = jc_resource.modify_goods(cpy_id, user_id, goods_id=self.goods_id, goods_name=goods_name, goods_type=2)
                assert modify_goods["a"] == 200
                select_db = use_db.execute_sql(
                    f"SELECT goodsType FROM goods_info WHERE company_id = {
      
      cpy_id} AND id = {
      
      self.goods_id}")
                assert str(select_db[0]) == '2'
                logger.info(f"修改'{
      
      goods_name}'的商品信息成功")

            logger.info(f"开始删除商品'{
      
      goods_name}'")
            with allure.step("调用删除商品接口"):
                del_goods = jc_resource.delete_goods(cpy_id, user_id, goods_id=self.goods_id)
                assert del_goods["a"] == 200
                select_db = use_db.execute_sql(
                    f"SELECT * FROM goods_info WHERE id = {
      
      self.goods_id}")
                print(select_db)
                logger.info(f"删除商品'{
      
      goods_name}'成功")
        except AssertionError as e:
            logger.info(f"商品流程测试失败")
            raise e

In the above smoke test case test_01_goods_flow, the three interfaces of adding, modifying, and deleting goods are verified at the same time, forming a short business flow. If the interfaces are smooth, the goods will be deleted at the end without manual maintenance.

Note:
The above module interfaces and test cases are for demonstration purposes only, and do not exist in reality.

In the traditional test case design mode, some instantiations are placed in setup or setup_class, such as: jc_resource = JcResource(xxx), but because the fixture function cannot be passed in the front and rear methods, some instantiation operations should be performed in the fixture function, and a memory address is returned, which is directly passed to the test case, so that the test case can call the business API in the instance object.

5. Run the project

After completing command line parameters, parsing strategies, encapsulating interfaces, and writing test cases, you can either directly click the run button in the editor to execute the test, or drive the execution on the command line. The following demonstrates the command line execution use case method:

-v: print the detailed execution process;
-s: the print statement in the console output use case;
–env: the command line parameter defined by pytest_addoption, the default value: test, the input range choices=[“dev”, “test”, “pre”]

Enter a non-existent --env parameter

pytest -v -s --env online test_jc_smoke.py

At this time, we will be prompted that the parameter is wrong, and online is an unavailable option.

C4

Run the test environment

pytest -v -s --env test test_jc_smoke.py

For convenience, the test case of the existing project is run directly, and when the test is passed in, it will run in the test environment.

C5

A total of 12 test cases, all run through:

C6

At the same time, the test results are sent to the enterprise WeChat group.

C7

Run development and staging environments

pytest -v -s --env dev test_jc_smoke.py  # 开发环境
pytest -v -s --env pre test_jc_smoke.py  # 预发布环境

The dev and pre parameters are received normally, but because the development and pre-release environments are not started, the execution fails.

C8

C9

6. Pytest realizes a summary of the principle of one-key switching environment scheme

Principle description:
The test environment variables are provided by the user input;
the test framework defines the test data analysis function, and according to the test variables input by the user, parses and returns the content of the data file corresponding to the test environment;

The following is the most complete software test engineer learning knowledge architecture system diagram in 2023 that I compiled

1. From entry to mastery of Python programming

Please add a picture description

2. Interface automation project actual combat

Please add a picture description

3. Actual Combat of Web Automation Project

Please add a picture description

4. Actual Combat of App Automation Project

Please add a picture description

5. Resume of first-tier manufacturers

Please add a picture description

6. Test and develop DevOps system

Please add a picture description

7. Commonly used automated testing tools

Please add a picture description

Eight, JMeter performance test

Please add a picture description

9. Summary (little surprise at the end)

As long as you move forward firmly towards the goal and work hard, the difficulties will become small and success will become within reach. Don't pursue overnight fame, but insist on accumulation and progress, you will be able to create your own brilliance! Believe in yourself, fight bravely, the future is up to you!

No matter how long and tortuous the front is, as long as you have an unyielding heart, never give up spirit, and work hard, you will be able to find your own path and achieve success. Believe in your ability, persist in chasing your dreams, and the future will shine brightly for you!

Even in dark moments, we must maintain faith and perseverance, go forward bravely, and unswervingly pursue our dreams. Every effort is accumulation, every persistence is growth, as long as you don't stop struggling, the dawn of success will come! Believe in yourself, dare to take risks, and create your own brilliance! come on!

Guess you like

Origin blog.csdn.net/x2waiwai/article/details/131831817