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 name | Request method | Required 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:
- 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
- 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:
- 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
- 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 name | Request 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:
- Be careful not to make mistakes in words, such as investor_phon et al
- When the investment amount is blank, the entire amount field can be left blank, instead of being written as "amount":
- 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