Python unittest 中使用mock

没错,我似乎是喜欢上了unittest,最近非常痴迷于研究测试代码,这个问题也是在最近做测试中碰到的一个实际问题,这里就这个问题的解决方式做一个简单的记录。

具体的问题是这样的,我的代码会用到一些和PC硬件交互的一些代码,比如从串口读写数据,这个时候我们不能每次测试的时候在测试机器上搞一个串口,另外我还会用到和硬件相关的Python库,比如树莓派的RPi.GPIO,这个库在PC上是无法安装的,这个时候我们就需要在测试的时候通过一些特殊的方式骗过测试程序,让测试程序忽略这些硬件调用。

嗯,问题的基本结局方式就是和前面描述的一样,那么实际写起来怎么玩呢?实际上unittest中已经集成了相关的方法,用于Mock一些测试中不好实现的操作,或者尚未实现的操作,比如单元测试中会用到的其它模块的接口,我其实对于测试这门学问了解并不多,有机会多了解一下,这里就是瞎扯几个名词,也不知道对不对。这里的mock并不是嘲笑的意思,而是形容词模拟的,没错,就是模拟一些函数,并生成相应的模拟返回值,unittest中进行Mock操作主要用到Mock和patch两个基本类。

Mock serial.Serial

比如在串口测试中我们会用到serial.Serial来打开一个串口,这个时候我们就可以通过如下的形式对这个串口进行模拟:

    @patch('serial.Serial')
    def test_uart_cmd(self, mock_serial):
        cmd = Command("uart open")
        # call serial.Serial.open() won't cause any exception
        self.uart.execute(cmd)
        self.assertCommandPass(cmd)
        cmd = Command("uart close")
        self.uart.dev = Mock()
        self.uart.execute(cmd)
        # we could also check if uart clock called
        self.uart.dev.close.assert_called_with()

在上面例子中我们通过patch将系统库serial.Serial替换成一个Mock,生成的mock作为一个新的参数变成mock_serial所以后面调用打开串口的函数时候不会产生任何的异常,因为所有的操作都变成模拟操作,中间不会涉及任何的硬件操作,而和硬件无关的逻辑还是可以正常测试的,比如命令会不会正确的运行。

Mock RPi.GPIO

嗯,这个挑战就稍微大了一点点,因为系统中没有这个库,所以测试程序导入相应的模块的时候就会出现异常,因为相应的模块会调用RPi.GPIO这个就比较麻烦了,不过也不是没有解决方法,Mock可以直接替换系统中的库列表,比如:

from unittest.mock import MagicMock, patch
MockRPi = MagicMock()
modules = {
    "RPi": MockRPi,
    "RPi.GPIO": MockRPi.GPIO,
}
patcher = patch.dict("sys.modules", modules)
patcher.start()

在引入了上面的代码之后,RPi和RPi.GPIO都成为模拟的mock库,这个时候你实际可以调用RPi和RPi.GPIO内部的任意方法,即使不存在的方法都没有任何问题,不过我肯定不建议这么做,因为我们所以通过mock的方式去测试RPi,我们实际就认为RPi是一个没有问题的库,当然他们应该有自己的测试,所以我们这里只需要着重测试我们自己的代码就可以了,RPi和RPi.GPIO可以直接模拟运行。

有了上面代码之后,测试代码和被测试模块在导入RPi和RPi.GPIO就不会触发任何的异常,我们的代码也就可以正常测试下去了。


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章

发表新评论