Implement automated interface testing based on Pytest+Requests+Allure!

1. Overall structure

  • Framework composition: pytest + requests + allure
  • Design Patterns:
    • keyword driven
  • Project structure:
    • Tool layer: api_keyword/
    • Parameter layer: params/
    • Use case layer: case/
    • Data driver: data_driver/
    • Data layer: data/
    • Logic layer: logic/

2. Specific steps and code

1. Tool layer
Secondary encapsulation of common behaviors such as get and post.
The code (api_key.py) is as follows:

import allure
import json
import jsonpath
import requests

# Define a keyword class
class ApiKey:
    # Encapsulate the get request behavior
    @allure.step("Send get request")
    def get(self, url, params=None, **kwargs):
        return requests.get(url=url, params=params, **kwargs)

    # Encapsulate the post request behavior
    @allure.step("Send post request")
    def post(self, url, data=None, **kwargs):
        return requests.post(url=url, data=data, **kwargs)

    # Since interfaces may be related to each other, the next interface requires a certain return value from the previous interface. Here, jsonpath is used to locate and obtain the value returned by the previous interface.
    @allure.step("Get the returned result dictionary value")
    def get_text(self, data, key):
        # Convert json data to dictionary
        json_data = json.loads(data)
        #jsonpath value
        value = jsonpath.jsonpath(json_data, '$..{0}'.format(key))
        return value[0]
  • The allure.step() decorator is quoted to describe the steps in detail to make the test report more detailed.
  • Use jsonpath to get the return value of the interface.

2. Data layer
The data uses yaml files.
The code (user.yaml) is as follows:

-
  user:
    username:admin
    password: '123456'
  msg: success
  title: Enter the correct account number and password, and the login is successful
-
  user:
    username:admin1
    password: '1234561'
  msg: Wrong username or password
  title: Enter wrong account number 1, password 1, login failed
-
  user:
    username:admin2
    password: '1234562'
  msg: Wrong username or password
  title: Enter wrong account number 2, password 2, login failed
  • The title is to dynamically obtain the parameters and generate the title when the use case is in progress.

3. Data-driven layer
Read and write data.
The code (yaml.driver.py) is as follows:

import yaml


def load_yaml(path):
    file = open(path, 'r', encoding='utf-8')
    data = yaml.load(file, Loader=yaml.FullLoader)
    return data

4. Parameter layer
The parameter layer stores publicly used parameters and calls them when used.
The code (allParams.py) is as follows:

'''
    rule:
    Global variables are represented by uppercase letters
'''

# address
URL = 'http://39.98.138.157:'

#port
PORT = '5000'
Now I have also found a lot of test friends and created a communication group to share technology, sharing a lot of technical documents and video tutorials we collected.
If you don’t want to experience the feeling of not being able to find resources when studying on your own, having no one to answer your questions, and persisting for a few days before giving up.
You can join us to communicate. And there are many technical experts who have made certain achievements in automation, performance, security, test development, etc.
Share their experience, and also share many live lectures and technical salons
You can learn for free! Focus on it! Open source! ! !
QQ group number: 110685036

5. Logic layer

Use case 1: Make a login interface request. Here, the login request sets three different sets of data in the yaml file for request.

Use case 2: Make an interface request for personal query. Here you need to use the token value returned by the login interface.

Use case three: Make an interface request to add products to the shopping cart. Here you need to use the token value returned by the login interface and the openid and userid values returned by the personal query interface.

Use case 4: Make an interface request for placing an order. Here you need to use the token value returned by the login interface and the openid, userid, and cartid values returned by the personal query interface.

Note: Since most interfaces need to use the token value returned by the login interface, a conftest.py is encapsulated to define the project-level prefix fixture, which is only executed once in the entire project and can be called in each use case (other shared parameters can also be similar) predefined). At the same time, due to the project-level fixture defined here, the initialization tool class ak = ApiKey() can also be placed in it.

The code (conftest.py) is as follows:

from random import random

import allure
importpytest

from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


def pytest_collection_modifyitems(items):
    """
    When the test case collection is completed, the Chinese name and nodeid of the collected item will be displayed on the console.
    """
    for item in items:
        item.name = item.name.encode("utf-8").decode("unicode_escape")
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")


# Project-level fix, the entire project is only initialized once
@pytest.fixture(scope='session')
def token_fix():
    #Initialize tool class
    ak = ApiKey()
    with allure.step("Send a login interface request and obtain the token. The entire project is only generated once"):
        # Request interface
        # url = 'http://39.98.138.157:5000/api/login'
        url = URL + PORT + '/api/login'
        # Request parameters
        userInfo = {
            'username': 'admin',
            'password': '123456'
        }
        # post request
        res = ak.post(url=url, json=userInfo)
        # Get token
        token = ak.get_text(res.text, 'token')
        # Verification code, verification token is only generated once
        token_random = random()
        return ak, token, res, token_random
  • It also includes preventing Chinese garbled characters and adding pytest_collection_modifyitems (function).

After setting up conftest, it can be applied in the logical layer.
The code (shopingApi.py) is as follows:

import pytest
import allure
from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


classApiCase():
    # Login logic
    def params_login(self, userdata):
        # Dynamically obtain parameters to generate titles
        allure.dynamic.title(userdata['title'])
        #Initialize tool class
        ak = ApiKey()
        # Request interface
        url = URL + PORT + '/api/login'
        # Request parameters
        userInfo = {
            'username': userdata['user']['username'],
            'password': userdata['user']['password']
        }
        res = ak.post(url=url, json=userInfo)
        with allure.step("Interface return information verification and printing"):
            print("/api/login login interface request response information")
            print(res.text)
            # Get response results
            msg = ak.get_text(res.text, 'msg')
            print(msg)
            # assertion
            assert msg == userdata['msg']

    def params_getuserinfo(self, token_fix):
        # Obtain preset tool classes and tokens from fix, all return values need to be received
        ak, token, res, token_random01 = token_fix
        with allure.step("Send personal query interface request"):
            url = URL + PORT + '/api/getuserinfo'
            headers = {
                'token': token
            }
            res1 = ak.get(url=url, headers=headers)
        with allure.step("Interface return information verification and printing"):
            print("/api/getuserinfo personal user query interface request response information")
            print(res1.text)
            # print("Verified random value, for testing")
            # print(token_random01)
            name = ak.get_text(res1.text, 'nikename')
            # assertion
            assert "风清阳" == name
        return res1

    def params_addcart(self, token_fix):
        # Get preset tool classes and tokens from fix
        # All returns must be obtained, otherwise an error will be reported
        ak, token, res, token_random01 = token_fix
        with allure.step("Call the getuserinfo interface to obtain the return information"):
            res1 = self.params_getuserinfo(token_fix)
        with allure.step("Send a request to add items to the shopping cart"):
            # Add products to the shopping cart based on token, userid, openid, productid
            url = URL + PORT + '/api/addcart'
            hd = {
                "token": token
            }
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888
            }
            # send request
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("Interface return information verification and printing"):
            print("/api/addcart add items to shopping cart request response information")
            print(res2.text)
            # print("Verified random value, for testing")
            # print(token_random01)
            result = ak.get_text(res2.text, 'result')
            assert 'success' == result
        return res2

    def params_createorder(self, token_fix):
        ak, token, res, token_random01 = token_fix
        with allure.step("Call the addcart interface to obtain return information"):
            res1 = self.params_addcart(token_fix)
        with allure.step("Send order request"):
            url = URL + PORT + '/api/createorder'
            # Get token from project-level fix
            hd = {
                "token": token
            }
            # Obtain userid, openid, cartid from the add product to shopping cart interface
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888,
                "cartid": ak.get_text(res1.text, 'cartid')
            }
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("Interface return information verification and printing"):
            print("/api/createorder order request response information")
            print(res2.text)
            # print("Verified random value, for testing")
            # print(token_random01)
            result = ak.get_text(res1.text, 'result')
            assert 'success' == result

6. Use case layer
Call the logic layer for use case management and data transmission.
The code (test_Tree.py) is as follows:

import allure
importpytest
from pytest_demo_2.data_driver import yaml_driver
from pytest_demo_2.logic.shopingApi import ApiCase


@allure.epic("shopXo e-commerce platform interface-interface test")
class TestTree():
    #Initialize use case library
    actions1 = ApiCase()

    @allure.feature("01.Login")
    @allure.story("02.General Scenario")
    @pytest.mark.parametrize('userdata', yaml_driver.load_yaml('./data/user.yaml'))
    def test_case01(self, userdata):
        self.actions1.params_login(userdata)

    @allure.feature("02.Personal query")
    @allure.story("01.Typical scenario")
    @allure.title("Personal Query")
    def test_case02(self, token_fix):
        self.actions1.params_getuserinfo(token_fix)

    @allure.feature("03.Add items to shopping cart")
    @allure.story("01.Typical scenario")
    @allure.title("Add items to shopping cart")
    def test_case03(self, token_fix):
        self.actions1.params_addcart(token_fix)

    @allure.feature("04.Place an order")
    @allure.story("01.Typical scenario")
    @allure.title("Place an order")
    def test_case04(self, token_fix):
        self.actions1.params_createorder(token_fix)

7. Operation
The code (main_run.py) is as follows:

import os
importpytest


def run():
    pytest.main(['-v', './case/test_Tree.py',
                 '--alluredir', './result', '--clean-alluredir'])
    os.system('allure serve result')
    # os.system('allure generate ./result/ -o ./report_allure/ --clean')

if __name__ == '__main__':
    run()

8. Results

If you find it useful, please follow, like, watch, and share it with your friends.