Open3

モックしたい(unittest.mock)

shotakahashotakaha

PySerialをモックする

read_data.py
def read_data(port: serial.Serial):
    data = port.readline().decodt("UTF-8").strip().split()
    return data
  • USBポートに接続した測定器からデータを読み出す関数
  • 測定器からは値1 値2 値3 値4のようなスペース区切りの文字列が送られてくる
test_read_data.py
from unittest.mock import MagicMock

def test_read_data():
    mock_port = MagicMock()
    mock_port.readline().decode.return_value = "値1 値2 値3 値4"

    data = read_data(mock_port)
    assert isinstance(data, list)
  • ユニットテストでは、ポートに接続せずにテストしたい
  • SerialオブジェクトをMagicMockで作成する
  • テストしたい関数と同じ処理をMagicMockオブジェクトに追加する
  • return_valueに返したい値を設定する
  • ユニットテスト用の関数の引数にMagicMockオブジェクトを指定する
shotakahashotakaha

pathlib.Pathをモックする

from pathlib import Path
import serial

def save_data(fname: Path, port: serial.Serial, max_rows: int):
    events = []
    with fname.open("a") as f:
        for _ in range(max_rows):
            event = read_data(port)
           events.append(event)
            f.write(events + "\n")
        f.flush()
   return events
  • max_rows行のデータをファイルに保存する関数
from unittest.mock import MagicMock, patch, mock_open

@patch("pathlib.Path.open", new_callable=mock_open)
def test_save_data(mock_open):
    # Pathオブジェクトをモックする
    fname = Mock()

    # Serialオブジェクトをモックする
    port = Mock()
    port.readline().decode.return_value = "値1 値2 値3 値4"

    max_rows = 5
    events = save_data(fname, port, max_rows)

    # 検証
    assert len(events) == max_rows
    mock_open.assert_called_once_with("a")
    handle = mock_open()
    assert handle.write.call_count == max_rows
    assert handle.flush.call_count == max_rows
  • ファイル操作がある関数をテストする場合、ファイルを開く関数をモックする
  • pathlibの場合は、pathlib.Path.openmock_openに置き換える
  • @patchデコレータで上書きする
  • mock_open.assert_called_once_with("a")で、fname.open("a")が1回だけ呼ばれたことごチェック
  • 関数名.call_countで該当する関数を呼び出した回数を確認できる