Today, let's talk about the use of asynchrony in python automated testing

Posted by binarymonkey on Fri, 07 Jan 2022 09:20:53 +0100

Asynchronous has been used in the project for a long time. For example, the web framework fastapi supports asynchronous writing. However, I only learned the writing method of "async/await", but can this writing method really make your program faster?

Asynchronous concept

  • synchronization

  • asynchronous

Synchronous VS asynchronous

Take a simulated visit to a website as an example to demonstrate the difference between synchronous and asynchronous.

  • synchronization

import time


def visit_url(url, response_time):
    """
    visit url
    """
    print(f"visit: {time.time()} - {url}")
    time.sleep(response_time)
    print(f"response: {time.time()}")
    return f"visit{url}, Returned results"


def run_task():
    visit_url('http://itest.info', 2)
    visit_url('http://www.testpub.cn', 3)


start_time = time.perf_counter()
run_task()
print(f"Elapsed time:{time.perf_counter() - start_time}")

Visit two websites respectively to simulate different time-consuming. The operation results are as follows:

visit: 1630638757.1427643 - http://itest.info
response: 1630638759.1537898
visit: 1630638759.1537898 - http://www.testpub.cn
response: 1630638762.1578455
 Elapsed time: 5.0148674
  • asynchronous

Next, use asynchronous writing, rewrite the example, and add {async/await.

import asyncio
import time


async def visit_url(url, response_time):
    """visit url"""
    print(f"visit: {time.time()} - {url}")
    await asyncio.sleep(response_time)
    print(f"response: {time.time()}")


async def run_task():
    await visit_url('http://itest.info', 2)
    await visit_url('http://www.testpub.cn', 3)


start_time = time.perf_counter()
asyncio.run(run_task())
print(f"Elapsed time:{time.perf_counter() - start_time}")

  • Add {async to the function.

  • await is required to call async function.

Run the program again:

visit: 1630639557.2313683 - http://itest.info
response: 1630639559.235232
visit: 1630639559.235232 - http://www.testpub.cn
response: 1630639562.2393005
 Elapsed time: 5.0091551999999995

You will find that there is no difference between the two runs.

If you want to achieve concurrency, you can create tasks through gather(). Modify run_task() function.

async def run_task():
    url1 = visit_url('http://wangzhen.com', 2)
    url2 = visit_url('http://www.testpub.cn', 3)

    await asyncio.gather(url1, url2)

asyncio. The gather () method wraps multiple asynchronous tasks (two URLs) into a new asynchronous task.

Run the program again:

visit: 1630640450.1746979 - http://itest.info
visit: 1630640450.1746979 - http://www.testpub.cn
response: 1630640452.1871428
response: 1630640453.1928813
 Elapsed time: 3.0196878000000003

From the results, it achieves the effect of concurrency. This is similar to the multi threading effect of python, but the asynchrony of Python is based on coroutines # to achieve concurrency.

Asynchronous test framework

Finally, we enter the topic and introduce the IsolatedAsyncioTestCase} class in python 3.8 unittest to realize asynchronous automatic testing.

from unittest import IsolatedAsyncioTestCase

class Test(IsolatedAsyncioTestCase):


    async def asyncSetUp(self):
        print("asyncSetUp")

    async def test_response(self):
        print("test_response")

    async def asyncTearDown(self):
        print("asyncTearDown")


if __name__ == "__main__":
    unittest.main()

At the syntactic level, async is added to each method. In order to verify this writing method, we introduce the httpx asynchronous HTTP library to fill in a specific use case.

import unittest
import httpx


class AsyncTest(unittest.IsolatedAsyncioTestCase):

    async def asyncSetUp(self):
        self.cli = httpx.AsyncClient()

    async def test_response(self):
        response = await self.cli.get("http://127.0.0.1:5000/")
        self.assertEqual(response.status_code, 200)

    async def test_response2(self):
        response = await self.cli.get("http://127.0.0.1:5000/")
        self.assertEqual(response.status_code, 200)

    async def asyncTearDown(self):
        await self.cli.aclose()


if __name__ == "__main__":
    unittest.main()

I started a web service locally and set the interface to return in 2 seconds.

Run the above use case.

..
----------------------------------------------------------------------
Ran 2 tests in 4.097s

OK

From the results, it takes 4 seconds to call the two interfaces, which is no different from synchronization. Moreover, we can't simply transform it into a "concurrent" form, which will affect the statistics of test results, report generation and so on.

summary

  1. Standing in the dimension of a single use case, the execution steps of the use case are sequential. I can't operate the page elements without opening the browser. Asynchronous writing is almost asexual.

  2. From the perspective of multiple use cases, concurrency} can effectively improve the running speed, but it depends on whether the test framework supports concurrency. If concurrency is not supported, even if multithreading is forced to run use cases, what about the collection and statistical report of use case results? In addition, if concurrency is used, each use case must be absolutely independent, because different use cases must be thrown into different threads for execution. Therefore, there can be no use case dependency. For web UI automation, the browser must be opened / closed once for each use case, which will increase the resource consumption.

  3. Asynchrony is mainly used in high concurrency scenarios, such as web services, web crawlers, database IO, etc. using asynchrony will significantly improve performance.

  4. Why does unittest support asynchrony? It is not to use asynchrony to improve its execution speed, but to help you write test cases of asynchronous code more conveniently.

  5. We see asynchrony in more and more projects, such as httpx, fastapi, aiofiles, aiomysql, playwright It is enough to show that he is a trend of programming in the future.

Topics: Python Programmer software testing