Recently, when writing interface test scripts, I encountered the following test scenario
1. System A will create A piece of data, which will be pushed to system B after successful creation;
2. Due to the communication between the two systems, the data will not be synchronized from system A to system B immediately, with A short time difference;
There are two interfaces I want to debug. One is to call an interface in system A to generate data; Second, system B calls another interface to process data.
After the actual operation, I found A problem: after calling interface A, interface B will be called immediately. From the code level, this time difference is very short. If the data has been generated by system A, but has not been pushed to system B in such A short time, resulting in the failure to find this data when calling interface B, an error will be reported
First solution
The initial solution is to use time.sleep(). After calling interface A, wait for A period of time, such as time.sleep(5), wait for 5s, and then call interface B
Because after waiting for 5s, the data can generally be pushed from system A to system B
Of course, if it is not synchronized to system B after 5s, an error will still be reported when calling the B interface, so this is not a good solution
Second solution
After surfing the Internet, I found that python has a library that can implement the retry mechanism: tenacity
Here are some reference blogs found. You can see the basic usage: https://www.cnblogs.com/wuzhibinsuib/p/13443622.html
Next, let's talk about our experimental results and understanding
Its simple usage is to add the @ retry modifier to the code that needs to be retried. The exception thrown by the code will be caught by the decorator and retried
The key here is to catch the exception thrown by the code
Example 1 [ if an error is reported, the system will try again all the time ]
@retry def test_retry1(): print("Wait for retry.....") raise Exception # adopt raise Returns an error directly @retry def test_retry2(): print("Wait for retry.....") return "hello" + 1 # A man-made error. Here I add strings and integers. Because of different types, an error will be reported, so a retry will be triggered
After the above two codes are run, the "wait for retry" will be printed until the operation is stopped manually
Example 2 [set the maximum number of retries stop_after_attempt]
@retry(stop=stop_after_attempt(5)) def test_retry(): print("Wait for retry.....") return "hello" + 1
Receive with stop stop_after_attempt: when retrying the specified number of times, the retrying ends, as follows: 5 retries
Example 3 [set the maximum restart time. If it fails, retry for 5s]
@retry(stop=stop_after_delay(5)) def test_retry(): print("Wait for retry.....") return "hello" + 1
Example 4 [ retry after specific error ]
@retry(retry=retry_if_exception_type(TypeError)) def test_retry1(): print("Wait for retry.....") return "hello" + 1 # Capture type error. Try again when type error occurs @retry(retry=retry_if_exception_type(SyntaxError)) def test_retry2(): print("Wait for retry.....") raise SyntaxError # Catch syntax errors and try again when syntax errors occur
Example 5 [ retry after meeting the customized conditions ]
# First, a function is defined symbol,Its function is to judge whether the incoming value is None;It returns a Boolean value if the result value=None,Then return true,Otherwise return False def symbol(value): return value is None # In the decorator retry=retry_if_result(symbol),Express handle test_retry The result of the function is passed in symbol,judge test_retry Is the result of None,
# If = None, retry. If it is not equal to None, end and return the function value (so the condition for retry is whether the result of test_retry is the result defined by the condition function) @retry(stop=stop_after_attempt(3), retry=retry_if_result(symbol), reraise=True) def test_retry(): print("Wait for retry.....") return None
The symbol() function is a defined conditional function, test_ The retry () function is the function that you want to retry. It is passed through the retry in the decorator_ if_ Result (), see the comments of the above code for the specific meaning
Next, start processing my interface test script and try again using the custom conditions in example 5 above
First, handle the method that needs to be retried. I specify that when this method does not receive the pushed data, it returns None
def seal_regist(code): seal_data = self.get_seal_data(code) try: if seal_data["data"]["list"]: r = requests.post(url, json=payload, headers=headers) return r.json() else: print("There is no data to be printed in the list{},Please check the system~".format(code)) return None except Exception as e: raise e
Define a conditional function
def test_retry(value): """Retry condition function""" return value is None
to seal_ Register() function plus retry decorator
@retry(stop=stop_after_delay(10), retry=retry_if_result(test_retry)) def seal_regist(code):
.....
.....
If seal_ If register() returns None, retry. The maximum retry time is 10s
ps. because the login cookie is required in the retry function, the method of obtaining the login cookie was written in before. However, if the retry mechanism is added, the login cookie will be obtained again when the retry starts, indicating that the login is frequent and the login interface call fails. Therefore, in order to avoid this situation, I put the method of obtaining the login cookie outside, In this way, no matter how many times you retry, you can use a cookie obtained at the beginning (so if you encounter a situation similar to mine, put out those methods that only need to obtain data once to avoid repeated requests and exceptions caused by the interface)