Bagaimana cara melakukan pengujian unit fungsi asinkron?

Saya menggunakan Bleak untuk menemukan & menghubungkan ke perangkat Bluetooth Low Energy (BLE) terdekat, dan saya Saya sedang menulis unit test (menggunakan pytest).

Saya baru mengenal tes Python dan saya tidak tahu apa yang harus dilakukan dengan patch/mock ini agar berfungsi pada fungsi async.

Saya tidak tahu apakah saya harus menggunakan fungsi sebenarnya, atau menerapkan tambalan pada fungsi default agar pengujian dapat dijalankan tanpa dongle BLE.

Berikut ini contoh kode (perbaikan dari discover.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())

Saya bertanya-tanya apakah saya harus menulis ulang fungsi untuk memindahkan bagian run() ke luar, dan mengejeknya.


person sodimel    schedule 18.06.2019    source sumber


Jawaban (2)


Fungsi luar list(op_sys) -> list tidak async karena melakukan panggilan ke loop.run_until_complete.

Sehingga seseorang dapat diuji unitnya seperti fungsi python sinkron lainnya.

Jika Anda ingin menguji unit fungsi asinkron seperti fungsi dalam run() -> list, lihat di sini: https://pypi.org/project/asynctest/.

person Freek Wiekmeijer    schedule 18.06.2019
comment
Jadi, dalam logika pengujian unit, saya tidak perlu menguji fungsi run? (terima kasih untuk asynctest, saya akan membacanya) - person sodimel; 18.06.2019
comment
Anda menyarangkan suatu fungsi ke dalam fungsi lain. Ini bagus untuk isolasi dan dapat membantu keterbacaan dan mengurangi pengulangan. Tapi logika yang kompleks harus berada di tempat di mana unittest dapat mengaksesnya. Ini terkadang merupakan pola yang kontradiktif. Bagi saya, fungsi ini sepertinya Anda harus mengejek bleak.discover dan menguji fungsi luar (sinkron)). - person Freek Wiekmeijer; 18.06.2019
comment
Saya menggunakan solusi dijelaskan di sini, dan pengujian saya berhasil. Saya akan memposting jawaban dengan kode pengujian saya. - person sodimel; 18.06.2019

Jadi dengan bantuan dari Freek, saya tahu itu Saya ingin mengejek bleak.discover, dan inilah cara saya melakukannya:

Saya menemukan solusi menggunakan jawaban ini dari Ivan.

Ini tes saya:

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"

Dan inilah hasil tesnya:

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

=== 1 passed in 0.09 seconds ===

Sunting: saran untuk kode elegan:

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])
        ....
person Community    schedule 18.06.2019