Business flow test

1. Analysis and coding of audit interface

The audit interface uses interface association method 1 (that is, all associated or dependent interfaces are used as preconditions for the tested interface. setUp or setupClass)

Audit interface analysis

Interface nameRequest methodRequired content
/member/login (borrower login)
post
/member/login (administrator login)post
/loan/add
post
member_id, token (borrower)
/loan/audit  patch loan_id, token (administrator)

Because there are two roles to log in, you need to add an administrator's test account

secret_ Modify the config.py code as follows:

# Borrower's test account number
user = {"mobile_phone": "15100002222", "pwd": "12345678"}
# Administrator test account
admin_user = {"mobile_phone": "15800002222", "pwd": "12345678"}

Modify the base.py code as follows:

import requests
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # We need to access the independent methods or properties used by the interface in the project
    @classmethod
    def login(cls):
        # Access login interface (ordinary user login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # Access login interface (administrator login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # Interface to add items
        json_data = {"member_id": "10",
                     "title": "Test item 55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

be careful:

  1. Because the added project interface uses the token and member after ordinary users log in_ ID, so add is called_ When using the project method, you need to pass in token and member_id
  2. When adding a project interface, remember to set the loan_id attribute, used by the audit interface

  The test case excel table contents are as follows:

  New test_ The audit.py code is as follows:

import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal

# Get test data
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')


@ddt
class TestAudit(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # Ordinary user login
        cls.login()
        # Administrator login
        cls.admin_login()
        cls.add_project(token=cls.token, member_id=cls.member_id)

    def setUp(self) -> None:
        # add item
        # self.add_project(token=self.token, member_id=self.member_id)
        pass

    @data(*excel_data)
    def test_recharge(self, info):
        log.info(f'Testing{info}')
        # info replace marked test data
        json_data = info['json']
        headers = info['headers']
        # Replace request body
        if "#loan_id#" in json_data:
            json_data = json_data.replace("#loan_id#", self.loan_id)
        # Replace request header
        if "#admin_token#" in headers:
            headers = headers.replace("#admin_token#", self.admin_token)
        # Convert to dictionary type
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        # Access recharge interface
        recharge_resp = requests.request(url=config.host + info['url'],
                                         method=info['method'],
                                         headers=headers,
                                         json=json_data).json()
        # Assert
        for key, value in expected.items():
            self.assertEqual(value, recharge_resp[key])

  Operation results:

Scenario 1: there are two test cases, one successful and the other failed (add project interface to setup)

Scenario 2: there are two test cases, one is success / failure, and the other is not in approval status (add the project interface to setupClass)

Note: add is determined according to different test cases_ Is the project method in setup or setupclass

Replace marked test data with regular expressions

Modify the base.py code as follows:

import requests
import re
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # We need to access the independent methods or properties used by the interface in the project
    @classmethod
    def login(cls):
        # Access login interface (ordinary user login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        # cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # Access login interface (administrator login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # Interface to add items
        json_data = {"member_id": "10",
                     "title": "Test item 55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

    @classmethod
    def replace_data(cls, string):
        # Replace the specified string with dynamic data
        result = re.finditer('#(.*?)#', string)
        for i in result:
            # i is each data matched
            old = i.group()  # #member_id#
            prop_name = i.group(1)  # member_id
            string = string.replace(old, str(getattr(cls, prop_name)))
        return string

Modify test_ The audit.py code is as follows:

import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal

# Get test data
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')


@ddt
class TestAudit(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # Ordinary user login
        cls.login()
        # Administrator login
        cls.admin_login()
        cls.add_project(token=cls.token, member_id=cls.member_id)

    def setUp(self) -> None:
        # add item
        # self.add_project(token=self.token, member_id=self.member_id)
        pass

    @data(*excel_data)
    def test_recharge(self, info):
        log.info(f'Testing{info}')
        # Replace with regular expressions json_data,headers in#  #Tag content
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # Convert to dictionary type
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        # Access recharge interface
        recharge_resp = requests.request(url=config.host + info['url'],
                                         method=info['method'],
                                         headers=headers,
                                         json=json_data).json()
        # Assert
        for key, value in expected.items():
            self.assertEqual(value, recharge_resp[key])

Operation results:

  Summary:

  1. Replace under regular expression APICase_ The data method needs to be replaced twice, json_data and headers, because both the request header and the request body have tags
  2. Call replace_ Before data, the attribute with the same name must be set in APICase. Here, loan is set_ id,admin_token

2. Analysis and coding of investment interface

The investment interface uses interface association method 2 (the business flow test method does not need to encapsulate various required interfaces as preconditions)

Investment interface analysis

Interface nameRequest method
/member/login (investor login)
post
/member/login (borrower login)post
/member/login (administrator login)post
/loan/add post  
/loan/audit
patch
/member/invest
  post 

The second method does not need to rely on other interfaces, so the required interfaces will be written into the test cases

Notes for writing test cases:

  1. Be careful not to make mistakes in words, such as investor_phon et al
  2. When the investment amount is blank, the entire amount field can be left blank, instead of being written as "amount":
  3. extractor is data extraction. It needs to be prepared for the next interface. When you log in to the investor interface, you get the investor_member_id and investor_token; When logging in to the borrower interface, get the loan_member_id and loan_token; When logging into the investor interface, get the investor_member_id,loan_token; When logging in to the administrator interface, get admin_member_id and admin_token; When accessing the add project interface, get the loan_id

Then we began to write the code. Because we involve three roles, we need accounts of different roles. Here, I log in the investor and the borrower as a test account, so secret_ The code of config.py does not need to be modified. If you log in with three accounts, add another one. The contents of the base.py module do not need to be modified, just call replace_ Just use the data method

The test investment interface code is as follows:

import unittest
import requests
import json
from jsonpath import jsonpath
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log

# Get test data
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')


@ddt
class TestInvest(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # Read the required data directly from the configuration file and set it to APICase attribute
        cls.investor_phone = secret_config.user['mobile_phone']
        cls.investor_pwd = secret_config.user['pwd']
        cls.loan_phone = secret_config.user['mobile_phone']
        cls.loan_pwd = secret_config.user['pwd']
        cls.admin_phone = secret_config.admin_user['mobile_phone']
        cls.admin_pwd = secret_config.admin_user['pwd']

    @data(*excel_data)
    def test_invest(self, info):

        # 1. Preprocess the data, access the interface, replace the data, and convert the string into a dictionary
        log.info(f'Testing{info}')
        # Replace with regular expressions json_data,headers in#  #Tag content
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # Convert to dictionary type
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        extractor = {}
        if info['extractor']:
            extractor = json.loads(info['extractor'])
        # 2. Access the interface to get the return result of the interface
        resp = requests.request(url=config.host + info['url'],
                                method=info['method'],
                                headers=headers,
                                json=json_data).json()
        # 3.Read the data required by the next interface in the response result and set it to##Tag attributes with the same name, such as investor_token
        # Get the investor login id and token
        # Set the ID to AIPCase.investor_member_id
        # Set the token to AIPCase.investor_token
        # Get $.. id,$..token through jsonpath
        # Set the attribute, and the attribute name should also be written in excel, such as investor_member_id
        for prcp_name, jsonpath_expression in extractor.items():
            value = jsonpath(resp, jsonpath_expression)[0]
            # Set class properties
            setattr(APICase, prcp_name, value)
        # 4. Assertions
        for key, value in expected.items():
            self.assertEqual(value, resp[key])

Operation results:

Note: because replace_ Data is encapsulated into class methods, so the data to be replaced should be written to class attributes, that is, to setupclass, because class methods cannot call instance methods (when replace_data is set as instance methods, the data to be replaced can be put into setup)

Next, let's continue to encapsulate the code and encapsulate the data replacement code into prev_ In the data method, the access interface is encapsulated in the visit method, the data extraction is encapsulated in the extract method, and the assertion is encapsulated in the assert method_ In all method

Modify the base.py code as follows:

import requests
import re
import json
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # We need to access the independent methods or properties used by the interface in the project
    @classmethod
    def login(cls):
        # Access login interface (ordinary user login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        # cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # Access login interface (administrator login)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # Interface to add items
        json_data = {"member_id": "10",
                     "title": "Test item 55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

    @classmethod
    def replace_data(cls, string):
        # Replace the specified string with dynamic data
        result = re.finditer('#(.*?)#', string)
        for i in result:
            # i is each data matched
            old = i.group()  # #member_id#
            prop_name = i.group(1)  # member_id
            string = string.replace(old, str(getattr(cls, prop_name)))
        return string

    def prev_data(self, info):
        # Data replacement
        # Replace with regular expressions json_data,headers in#  #Tag content
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # Convert to dictionary type
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        extractor = {}
        if info['extractor']:
            extractor = json.loads(info['extractor'])
        # Reset the replaced and converted data back to the key and value of the dictionary
        info['json'] = json_data
        info['headers'] = headers
        info['expected'] = expected
        info['extractor'] = extractor
        return info

    def visit(self, info):
        # Access interface
        resp = requests.request(url=config.host + info['url'],
                                method=info['method'],
                                headers=info['headers'],
                                json=info['json']).json()
        return resp

    @classmethod
    def extract(cls, info, resp):
        # Data extraction
        for prcp_name, jsonpath_expression in info['extractor'].items():
            value = jsonpath(resp, jsonpath_expression)[0]
            # Set class properties
            setattr(cls, prcp_name, value)

    def assert_all(self, info, resp):
        # Assert
        for key, value in info['expected'].items():
            self.assertEqual(value, resp[key])

    def steps(self, info):
        info = self.prev_data(info)
        resp = self.visit(info)
        self.extract(info, resp)
        self.assert_all(info, resp)

Modify the investment interface code as follows:

import unittest
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log

# Get test data
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')


@ddt
class TestInvest(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # Read the required data directly from the configuration file and set it to APICase attribute
        cls.investor_phone = secret_config.user['mobile_phone']
        cls.investor_pwd = secret_config.user['pwd']
        cls.loan_phone = secret_config.user['mobile_phone']
        cls.loan_pwd = secret_config.user['pwd']
        cls.admin_phone = secret_config.admin_user['mobile_phone']
        cls.admin_pwd = secret_config.admin_user['pwd']

    @data(*excel_data)
    def test_invest(self, info):
        log.info(f'Testing{info}')
        self.steps(info)

Operation results:

Summary:

When to use method 1 and when to use method 2

Method 1: setup operation and encapsulation. Each specific method is encapsulated into a function, which is suitable for less interface data and is not too troublesome

Method 2: it is suitable for a large number of interfaces, high force, difficult implementation, general processing flow and troublesome debugging

Tags: Python

Posted on Mon, 08 Nov 2021 02:49:29 -0500 by piersk