จะทำการทดสอบหน่วยของฟังก์ชันอะซิงโครนัสได้อย่างไร?

ฉันใช้ Bleak เพื่อค้นหาและเชื่อมต่อกับอุปกรณ์ Bluetooth Low Energy (BLE) ที่ใกล้ที่สุด และฉัน ฉันกำลังเขียนการทดสอบหน่วย (โดยใช้ pytest)

ฉันยังใหม่กับการทดสอบ Python และฉันไม่รู้ว่าต้องทำอย่างไรกับแพตช์/จำลองเหล่านี้เพื่อให้มันทำงานกับฟังก์ชัน async ได้

ฉันไม่รู้ว่าฉันควรใช้ฟังก์ชันจริง หรือใช้แพตช์กับฟังก์ชันเริ่มต้นเพื่อให้การทดสอบดำเนินการได้โดยไม่ต้องใช้ดองเกิล BLE

นี่คือตัวอย่างโค้ด (การปรับปรุง 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())

ฉันสงสัยว่าฉันควรเขียนฟังก์ชันใหม่เพื่อย้ายส่วน run() ออกไปข้างนอกและเยาะเย้ยมันหรือไม่


person sodimel    schedule 18.06.2019    source แหล่งที่มา


คำตอบ (2)


ฟังก์ชันภายนอก list(op_sys) -> list ไม่อะซิงก์เนื่องจากทำการเรียก loop.run_until_complete

เพื่อให้สามารถทดสอบหน่วยได้เหมือนกับฟังก์ชันหลามแบบซิงโครนัส

หากคุณต้องการทดสอบหน่วยฟังก์ชันอะซิงก์ เช่น ฟังก์ชันภายใน run() -> list โปรดดูที่นี่: https://pypi.org/project/asynctest/.

person Freek Wiekmeijer    schedule 18.06.2019
comment
ดังนั้นในตรรกะการทดสอบหน่วย ฉันไม่จำเป็นต้องทดสอบฟังก์ชัน run ใช่หรือไม่ (ขอบคุณสำหรับ asynctest ฉันจะอ่านมัน) - person sodimel; 18.06.2019
comment
คุณกำลังซ้อนฟังก์ชันเข้ากับฟังก์ชันอื่น วิธีนี้เหมาะสำหรับการแยกออกจากกันและสามารถช่วยให้อ่านง่ายและลดการซ้ำกัน แต่ตรรกะที่ซับซ้อนควรอยู่ในตำแหน่งที่ unittest สามารถเข้าถึงได้ สิ่งเหล่านี้บางครั้งเป็นรูปแบบที่ขัดแย้งกัน ฟังก์ชันนี้ดูเหมือนว่าคุณควรจำลอง bleak.discover และทดสอบฟังก์ชันภายนอก (ซิงโครนัส) - person Freek Wiekmeijer; 18.06.2019
comment
ฉันใช้โซลูชันที่ อธิบายไว้ที่นี่ และการทดสอบของฉันก็ใช้งานได้ ฉันจะโพสต์คำตอบพร้อมรหัสการทดสอบของฉัน - person sodimel; 18.06.2019

ดังนั้นด้วยความช่วยเหลือของ Freek ฉันจึงรู้ว่า ฉันอยากจะเยาะเย้ย bleak.discover และนี่คือวิธีที่ฉันทำ:

ฉันพบวิธีแก้ปัญหาโดยใช้ผู้ตอบนี้ของ Ivan

นี่คือการทดสอบของฉัน:

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