Как выполнить модульные тесты асинхронных функций? - PullRequest
1 голос
/ 18 июня 2019

Я использую Bleak , чтобы обнаружить и подключиться к ближайшему устройству Bluetooth с низким энергопотреблением (BLE), и в настоящее время я пишу юнит-тесты (используя pytest).

Яновичок в тестах Python, и я не знаю, что делать с этим патчем / макетом, чтобы он работал на async функциях.

Я не знаю, следует ли мне использовать реальную функцию или применять патчи кстандартные функции для выполнения теста без ключа BLE.

Вот пример кода (улучшение explore.py ):

def list(op_sys: str) -> list:
    """list BLE devices

    Returns:
        list: status & list or error message
    """
    import asyncio, platform
    from bleak import discover

    async def run() -> list:
        """discover BLE devices

        Returns:
            list: status & list or error message
        """
        BLElist = []
        try:
            devices = await discover()
            for d in devices:
                print("'%s'" % d.name) # list devices
                BLElist.append(d.name)
            return 'success', BLElist
        except:
            return 'error', 'You don\'t have any BLE dongle.'

    # linux = 3.6, windows = 3.7, need a new loop to work
    if op_sys == "Windows":
        asyncio.set_event_loop(asyncio.new_event_loop())

    loop = asyncio.get_event_loop()
    return loop.run_until_complete(run())

I 'Мне интересно, стоит ли мне переписать функцию, чтобы переместить деталь run() наружу и высмеять ее.

Ответы [ 2 ]

2 голосов
/ 18 июня 2019

Внешняя функция list(op_sys) -> list не является асинхронной, потому что она вызывает loop.run_until_complete.

, чтобы можно было выполнить модульное тестирование, как любая синхронная функция Python.

Если вы хотитедля модульного тестирования асинхронных функций, таких как внутренняя функция run() -> list, посмотрите здесь: https://pypi.org/project/asynctest/.

0 голосов
/ 18 июня 2019

Итак, с помощью справки Фрика я понял, что хочу высмеять bleak.discover, и вот как я это сделал:

Я нашел решение, используя этот ответ Ивана.

Вот мой тест:

import os, asyncio
from unittest.mock import Mock
from app.functions.ble import ble

class BLE:
    def __init__(self, name):
        self.name = name

# code of Ivan, thank you Ivan!
def async_return(result):
    f = asyncio.Future()
    f.set_result(result)
    return f

def test_list(monkeypatch):

    mock_discover = Mock(return_value=async_return([BLE("T-000001"), BLE("T-000002"), BLE("T-000003")]))
    monkeypatch.setattr('bleak.discover', mock_discover)
    list_BLE = ble.list("Linux")

    mock_discover.assert_called_once()
    assert list_BLE[0] == 'success'
    assert list_BLE[1][0] == "T-000001"

А вот и результат теста:

tests/test_ble.py::test_list 'T-000001'
'T-000002'
'T-000003'
PASSED

=== 1 passed in 0.09 seconds ===

Редактировать: предложение для элегантного кода:

from unittest import TestCase
from unittest.mock import patch
import os, asyncio

from app.functions.ble import ble


class DeviceDiscoveryTest(TestCase):

    @staticmethod
    def __async_return(result):
        f = asyncio.Future()
        f.set_result(result)
        return f

   @classmethod
   def mocked_discover(cls):
        return cls.__async_return([BLE("T-000001"), BLE("T-000002"), BLE("T-000003")])

    @patch('bleak.discocver', new=DeviceDiscoveryTest.mocked_discover)
    def test_discover_devices(self):
        list_BLE = ble.list("Linux")
        self.assertEquals('success', list_BLE[0])
        ....
...