Easy to understand --- build httprunner+pymysql+allure+jenkins+git interface automation test framework

The author of httprunner is debugtalk. He is a senior developer. At present, there are five versions of httprunner
The usage of Httprunner is relatively simple and conforms to a variety of scenarios in work:
1. After completing the interface automation, it is required to continue the performance test of this interface (hrun has a built-in locust module, which can directly conduct pressure test)
2. There are too many interfaces to do, and only some parameters need to be replaced, and the work of interface automation will be completed (charles records and exports the. har file, and converts it into a. py file through the hrun command)
3. Every step of the interface response of the request library needs to add assertions, and hrun will automatically generate assertion methods
4. Third party libraries integrated by hrun: loguru and jmespath

Current version of hrun:

Environment preparation: install python, Httprunner, allure pytest:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple httprunner
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple allure-pytest

Environment verification, check the httprunner version:

httprunner -V   


help:

httprunner --help  

Generate scaffolding (quickly generate a hrun framework):

httprunner startproject  PartsService


Convert the yml file in the scaffold into pytest file (execute in the root directory):

 cd  PartsService
 hmake testcases

Run httprunner and a single file (enter the root directory of the file to be run)

Mode 1:		pytest [filename]
Mode 2:		hrun [filename]
Mode 3:		httprunner run  [filename]

Run httprunner in batch to run all use cases under the file set

Mode 1:				pytest [dirname]
Mode 2:				hrun [dirname]
Mode 3:				 httprunner run  [dirname]

see. har generate script help

har2case -h  

The above is the basic usage of hrun, and the following is the secondary encapsulation of hrun

Allure: store the report result set of allure
api_data: store the request data of the interface (can be json file, xlsx file, yaml file)
api_testing: store API test cases
api_util: encapsulation method for API testing
common: public methods, such as reading database files
config: store global configuration files
databases: store database files
login: login

config file content:

[base]
# the url domain you wants to use in UI testing
backend_url =https://wwww.baidu.cn/

[report]
###  pytest xxxx --alluredir ./report
###  allure serve report

###   hrun test_post_api.yml --alluredir=allure
###   allure generate ./allure/ -o ./reports --clean

backend_url is the project address. If there are multiple sets of environments, you can put multiple URLs to switch

common file content:

import configparser  
import os


class ConfigReader:
    def __init__(self, file_name):
        self.config = configparser.ConfigParser()
        config_path = os.path.split(os.path.realpath(__file__))[0] + os.sep + file_name
        self.config.read(config_path, "utf-8")

    def get_config(self, config_item):
        sections = self.config.sections()
        for section in sections:
            if self.config.has_option(section, config_item):
                return self.config.get(section, config_item)

    def get_config_by_section(self, config_item, section_name):
        return self.config.get(section_name, config_item)

Only one method of reading Config file is written here, which is convenient for switching multiple sets of database environments. It can be supplemented later if necessary

login file content:

from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase  # todo: import httprunner module
def get_base_url():  # todo: encapsulate project_host method, which is the global API path of the project
    from common import config_reader  # todo:  config_reader reference config_reader method
    return config_reader.ConfigReader('../config/base_config.cfg').get_config('backend_url')


class TestCaseLogin(HttpRunner): # todo: define that the class name should start with Test

    config = Config("testcase description")\
        .verify(False)\   # todo: ignore Https certificates
        .variables(**{
            "Accept":"application/json, text/plain, */*",
            "Cookie":"SIAM_IMAGE_CODE=762410696575033344; SIAMTGT=TGT-892-4xraUmB3mrsA0MtWv11ENoZmq1t4CiBYgSGPJjG9NQrHpTqczc-SIAM; LtpaToken=AAECAzYxRjBCN0Y5NjFGMTYwQjlDTj1iaWVzb25nbGluL089Zm90b25UaDZzgyb50ZEjaw/jBYfTHXONKQ==",
            "userName": " xxxx",   
            "password": "xxxx",  
            "validCode": "xxx",
            "brandId":"x",
    })  # TODO: set global variables. The following Step methods can be used. Accept, Cookie, userName, password, validCode and brandId are set here

    teststeps = [
        Step(
            RunRequest("Sign in")
                .post(get_base_url()+"API route")  #todo: switch your own api path
                .with_cookies(**{"aaf875be9aad4feca52ccece8eade2df": "WyIzMTE4NjIwNDQ3Il0"}) # todo: API cookies
                .with_json({"userName": "$userName", "password": "$password", "validCode": "$validCode"}) # todo: reference the defined global variable
                .extract()
                .with_jmespath('body.data.token', 'token') # todo: get the response value from the response. The outermost layer is a fixed body
                .with_jmespath('headers.Server', 'cookies')  # todo: take the token under the data of the body for the variable name later, so the body is specified earlier. Similarly, you can also take the data of the header (if necessary)
                .validate()
                .assert_equal("status_code", 200)
                .assert_equal('headers."Content-Type"', "application/json")
                .assert_equal("body.code", 200)
                .assert_equal("body.message", "success")
        ),
    ]

if __name__ == "__main__":
    TestCaseLogin().test_start()

Write the login case to facilitate the subsequent hrun file call and realize login decoupling

api_util file contents:

import time
from httprunner import HttpRunner,Config,Step,RunTestCase,RunRequest
from login.get_token import TestCaseLogin as GetToken  # todo: import login file
def get_base_url():
    from common import config_reader
    return config_reader.ConfigReader('../config/base_config.cfg').get_config('backend_url')

class APIRequestConstructor(HttpRunner):  # todo: create a basic class to clear and read the token information of hrun later
    config = (
        Config("API Test execution")
            .base_url(get_base_url())
            .verify(False))
    teststeps = [
        Step(
            RunTestCase("By login API obtain token")
                .call(GetToken)
                .export(*["token",]))]
                
def reset_request_step(token=GetToken): # todo: encapsulates the method based on the class. If it is not passed by default, GetToken is used
    APIRequestConstructor.teststeps.clear() # todo: teardown, which belongs to the unit test framework, gives each use case a clean execution environment
    APIRequestConstructor.teststeps.append(  # todo: add step action
        Step(
            RunTestCase("By login API obtain token")
                .call(token)
                .export(*["token",])
        ))

headers = {        # todo: write header data according to the needs of the project
    "Host": "xxx",
    "Accept": "application/json, text/plain, */*",
    "Content-Type": "application/json;charset=utf-8",
    "brandId": "3",
    "Authorization": "${token}",
    "Cookie": "xxxx",
}
cookies = {"aaf875be9aad4feca52ccece8eade2df": "WyIxNjUxMTM1OTE1Il0"} # todo: write cookie data according to the needs of the project


def create_run_request(request_name, url, request_json, body="body.data", headers=headers, cookies=cookies):  # todo: encapsulate the use case steps and place the API name, request address and request parameters
    if request_json is not None: # todo: if the json format of the request parameter is not empty, execute the post request
        return RunRequest(request_name).post(url).with_headers(**headers).with_cookies(**cookies).with_json(
            request_json
        ).extract().with_jmespath(body, "res_data")
    else: # todo: if the request parameter json is empty, the post request will be executed
        return RunRequest(request_name).get(url).with_headers(**headers).with_cookies(**cookies) \
            .extract().with_jmespath(body, "res_data")

def contruct_request_step(run_request_obj):
    return Step(
        run_request_obj
    )

def perform_requests_and_get_last_response(steps):  # todo: the execution step of putting into use case
    APIRequestConstructor.teststeps.extend(steps)  # todo: steps to read teststeps
    obj = APIRequestConstructor()  # todo: instantiating objects
    obj.test_start()
    time.sleep(1)
    res = obj.with_export(["res_data", "token"]).get_export_variables()
    return res

Secondary encapsulation of httprunner to reduce redundant code

Contents of databases file:

import copy
import configparser
import os
class ConfigReader:
    def __init__(self, file_name):
        self.config = configparser.ConfigParser()
        config_path = os.path.split(os.path.realpath(__file__))[0] + os.sep + file_name
        self.config.read(config_path, "utf-8")

    def get_config(self, config_item):
        sections = self.config.sections()
        for section in sections:
            if self.config.has_option(section, config_item):
                return self.config.get(section, config_item)
class DB():
    def __init__(self, database_name):
        cf = ConfigReader("database_config_dev.cfg")  # todo: which database is connected? If there are multiple environments, remember to switch the database configuration here
        self.host = cf.get_config(database_name + "_host")
        self.port = cf.get_config(database_name + "_port")
        self.user = cf.get_config(database_name + "_user")
        self.password = cf.get_config(database_name + "_password")
        self.database_name = database_name
    def connect(self):  # todo: create database connection method
        import pymysql
        # Create database connection
        self.db = pymysql.connect(host=self.host, user=self.user, password=
        self.password, database=self.database_name)
        # Create cursor
        self.cursor = self.db.cursor()

    def get_one(self, sql):  # todo: return a piece of data
        result = 0
        try:
            self.connect()
            self.cursor.execute(sql)
            result = self.cursor.fetchone()
            self.close()
        except Exception as e:
            print('select error', e)
        return result

    def get_all(self, sql):  # todo: return all qualified query results
        result = 0
        try:
            self.connect()
            self.cursor.execute(sql)
            result = self.cursor.fetchall()
            self.close()
        except Exception as e:
            print("select error", e)
        return result

    def __edit(self, sql):  # Create main function
        result = 1  # Set the result set for judgment when calling
        try:  # Here is the try statement used to try the operation
            self.connect()
            self.cursor.execute(sql)
            self.db.commit()  # Note that if you modify, delete, or add a database, you must commit. You do not need to commit to query or create tables
            self.close()
        except Exception as e:  # If the operation fails, an operation exception is reported and the cursor is rolled back
            print('error :', e)
            result = 0
            self.db.rollback()
        return result

    def insert(self, sql):
        # The following three insert statements are the same, but we look clearer when calling
        return self.__edit(sql)  # Execute sql statements through the processing of the main function

    def delete(self, sql):  # Delete statement
        return self.__edit(sql)

    def update(self, sql):  # Modify statement
        return self.__edit(sql)

    def close(self):  # Closing method
        self.cursor.close()  # Close cursor
        self.db.close()  # close database



For example, there are two sets of database environments here. It is easier to encapsulate them with this method

api_ Content of testing file:

import allure,pytest
from loguru import logger
from api_util.util import *

@allure.feature("Parts information management query")
class TestSearChParts():
    @pytest.fixture(scope="class", autouse=True)
    @allure.feature("data dump")
    def teardown_(self):
        yield
        logger.debug("Implementation of the use case will end.")
    @allure.story("Branch: parts information management query")
    def test_Search_Parts_GetToken(self):
        reset_request_step()  # todo: login file reference. GetToken is not passed by default
        # todo: create request method create_search_parts can be named by user. Call create_run_request method, passing in API name, API address and request body
        create_search_parts = \
            create_run_request(
                "Accessory information management", "api/common/partsInfo/sparepart/v1/getSparePartByPage", {"status":1,"pageSize":10,"pageNum":1,"loading":"false"}) \
                .with_jmespath("body.data.list[0].id","ids") \
                .validate() \
                .assert_equal("status_code", 200).assert_equal("body.message", "success")
        # todo:   contruct_request_step is put into the execution step. If there are multiple steps, they need to be separated by commas
        create_out_of_warranty_payment_step = contruct_request_step(
            create_search_parts)
        # todo:   perform_ requests_ and_ get_ last_ Put the response into the use case that needs to be executed. Similarly, if there are multiple, they need to be separated by commas
        res = perform_requests_and_get_last_response(
            [create_out_of_warranty_payment_step])
        id = res['res_data']['list'][0]['id'] # todo: take out the response value of the interface response and get it to the dependent interface for use. Because of the encapsulated method, the external body of response is res_data, so you need to add
        print(id)
    @allure.story("Service station: parts information management query")
    def test_Search_Parts_Service_Station(self):
        reset_request_step(Service_Station)
        create_out_of_warranty_payment_run_request = \
            create_run_request(
                "Accessory information management", "api/common/partsInfo/sparepart/v1/getSparePartByPage",
                {"status": 1, "pageSize": 10, "pageNum": 1, "loading": "false"}) \
                .validate() \
                .assert_equal("status_code", 200).assert_equal("body.message", "success")
        create_out_of_warranty_payment_step = contruct_request_step(
            create_out_of_warranty_payment_run_request)
        res = perform_requests_and_get_last_response(
            [create_out_of_warranty_payment_step])

Last run file method:
1. Right click the file to run

2. Click the green triangle on the left side of the method to execute a single use case

3. Run with command:

pytest [filename]

You can also use hrun [filename]


If you want to run all the use cases under the whole file set, you can directly pytest [dirname]

Locally generated allure Report

pytest xxxx --alluredir ./report     # todo: xxx is the name of the hrun file or the name of the folder. Generate allure result set

allure serve report    # todo: view the allure report locally (the console will give the url address)

allure generate ./report/ -o ./reports --clean  # todo: clear the historical allure result set and add an allure report under the reports folder

jenkins configuring pytest+httprunner

1. To install the allure plug-in on jenkins:
Jenkins system management - global tool configuration - Allure Commandline add configuration
Check automatic installation, select from maven central, and select the latest version, which is currently 2.7

2. Configure job
New task: free style software project

2. Enter job configuration

Configure git
Repository URL: git address. Remember to add it after it git
Credentials: pull git code credentials


Click the [add] button to enter the jenkins add voucher pop-up window

**Branches to build: * * for branches, fill in * / master, master branch. If necessary, fill in other branches

cron timing configuration

Just click the question mark and see the help. It is set here to build a job at 6 a.m. every day

shell build
Select Execute shell here and execute with shell command. This is because Jenkins pulls the code in a fixed In the jenkins/workspace directory, the ${WORKSPACE} variable points to this address.
Finally save it!

Post build actions: after the action is built, click the add post build action option and select allow. The name filled in the path of Results should be the same as the name after ${WORKSPACE}, otherwise the report content is empty.

Run Jenkins

You can view the new task on the home page and click the small icon on the far right to build it. You can also click name to enter the task and click start construction. Click Allure Report to view the test results of this run.

Detailed Jenkins configuration: https://blog.csdn.net/weixin_42923777/article/details/102551493?ops_request_misc=&request_id=&biz_id=102&utm_term=jenkins%E9%85%8D%E7%BD%AEpytest +allure&utm_ medium=distribute. pc_ search_ result. none-task-blog-2allsobaiduweb~default-2-102551493. pc_ search_ result_ positive&spm=1018.2226.3001.4187

Keywords: Python software testing API testing

Added by HoangLong on Thu, 17 Feb 2022 14:59:52 +0200