Python unittest mock正确使用:解决含SSH依赖项目单元测试覆盖率低问题
Python SSH场景单元测试Mock正确用法
原有测试的核心问题
你当前测试覆盖率极低的核心原因是错误的mock了被测对象本身,具体问题如下:
- 你通过
@mock.patch('certificates.Certificates')把要测试的Certificates类整个替换成了模拟对象,所有调用都没有走你写的真实业务逻辑,自然覆盖率极低 - mock装饰器的参数顺序是从下往上匹配,你当前的参数顺序完全颠倒
- 没有模拟SSH依赖的完整返回结构,无法匹配业务代码的调用链路
- 没有覆盖异常分支的测试场景
正确实现思路
单元测试中我们只需要mock外部不稳定依赖,被测的业务类本身绝对不能mock,本次你只需要mock两个依赖:
- SSH连接工具类
SshConnection,避免真实发起SSH请求 - 环境变量
os.environ,避免测试依赖本地环境配置
完整测试代码
import os import unittest from unittest import mock from certificates import Certificates # 引入真实的被测类 # 只mock SSH依赖,不要mock被测的Certificates类 @mock.patch('helpers.ssh_connection.SshConnection') class TestCertificates(unittest.TestCase): def setUp(self): # 构造模拟的stdout对象,匹配业务代码中stdout.read().decode().strip()的调用链路 self.mock_stdout = mock.Mock() self.mock_stdout.read.return_value.decode.return_value.strip.return_value = "test_cert_content" def test_get_certificates_normal_case(self, MockSsh): # 配置模拟的SSH实例的execute_cmd返回值 mock_ssh_instance = MockSsh.return_value mock_ssh_instance.execute_cmd.return_value = (None, self.mock_stdout, None) # 实例化真实的被测对象 cert_obj = Certificates() result = cert_obj.get_certificates("test_env") # 断言结果符合预期 self.assertTrue(result) # 断言SSH命令被正确调用了2次,参数符合要求 mock_ssh_instance.execute_cmd.assert_has_calls( [ mock.call("get certificate_1"), mock.call("get certificate_2 by using test_env") ] ) def test_get_certificates_exception_case(self, MockSsh): # 配置execute_cmd调用时抛出RuntimeError,触发异常分支 mock_ssh_instance = MockSsh.return_value mock_ssh_instance.execute_cmd.side_effect = RuntimeError cert_obj = Certificates() result = cert_obj.get_certificates("test_env") # 断言异常分支返回符合预期 self.assertFalse(result) @mock.patch.dict(os.environ, { "IP_ADDRESS": "123.123.123.123", "SSH_KEY_PATH": "/test/ssh_key", "ENV_NAME": "prod_env" }) @mock.patch('certificates.Certificates.get_certificates') # 只mock本类的get_certificates方法 def test_main(self, mock_get_cert, MockSsh): mock_ssh_instance = MockSsh.return_value # 调用真实的main方法 Certificates().main() # 断言SSH连接的打开、关闭方法被正确调用 mock_ssh_instance.open_connection.assert_called_once_with("123.123.123.123", "/test/ssh_key") mock_ssh_instance.close_connection.assert_called_once() # 断言get_certificates被正确调用 mock_get_cert.assert_called_once_with("prod_env") if __name__ == '__main__': unittest.main()
关键Mock要点说明
- Mock对象定位原则:你要mock的是业务代码中实际import使用的对象路径,比如业务代码从
helpers.ssh_connection引入SshConnection,就patch这个路径 - 返回值结构匹配:模拟依赖的返回值要和真实调用的结构完全一致,比如
execute_cmd返回三个对象,stdout需要有read()方法,返回的字节对象要有decode()方法,整个链路要和真实调用对齐 - 分支覆盖:通过
side_effect配置异常抛出,覆盖try-except的异常分支 - 最小Mock原则:只mock必要的外部依赖,被测的业务逻辑尽量全部走真实代码,才能保证测试的有效性
内容的提问来源于stack exchange,提问作者Hemoi
相关产品推荐
相关产品推荐

