使用python包装的c ++ SWIG模拟输入

Posted

技术标签:

【中文标题】使用python包装的c ++ SWIG模拟输入【英文标题】:Mocking input using python wrapped c++ SWIG 【发布时间】:2017-08-29 08:46:28 【问题描述】:

我是 swig 和 python 单元测试的新手。

这就是我想要做的。我有一个需要用户输入的 c++ 函数。使用 SWIG 将 C++ 代码包装到 python 代码中。我正在尝试使用 pythons unittest 模块来模拟输入。我试过模拟 builtins.input 并用 c++ 编写我自己的基本函数,它只返回一个字符串并模拟它。

当我到达 c++ 代码中的 std::cin 时,Mocking builtins.input 仍然挂起。并模拟返回字符串的函数,不会返回模拟的 return_value。

我的猜测是由于某种原因我不能模拟函数的返回值,因为它是真正的 c++ 代码而不是真正的 python。

这是我正在使用的一些示例代码:

c++我可以包含头文件是需要的,但是真的很简单。

#include "MockTest.h"
#include <iostream>
#include <string>

MockTest::MockTest() 

std::string MockTest::getInput()

    std::string name;
    std::cout << "Enter you name" << std::endl;
    name = this->mockInput("hi"); //std::cin >> name;
    std::string toReturn = "Hello " + name + " person";
    return toReturn;


std::string MockTest::mockInput(std::string input)
 
    return input;

swig 接口文件:

%module MockTest
%include "std_string.i"
%include "std_vector.i"
%include "std_iostream.i"

%
#include "MockTest.h"
%
%include <MockTest.h>

python 测试脚本

from unittest.mock import patch
from unittest import TestCase
import unittest

import MockTest


class Test(TestCase):

    @patch('builtins.input', return_value="Will")
    def test_Mocktest(self, input):
        self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')

    @patch('MockTest.MockTest.mockInput', return_value="Will")
    def test_Mocktest2(self, mockInput):
        self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')

if __name__ == '__main__':
    unittest.main()

【问题讨论】:

我的 Python 版本中没有 unittest.mock,但是当删除补丁内容并重新引入 std::cin 时,这里一切正常。我很确定,使用来自unittest.mockpatch 修补C 程序的std::cin 是不可能的。您需要告诉二进制 (.so) 文件 STDIN 不再是 STDIN。 你如何构建你的模块(哪些是命令)?因为我得到一个不同的错误(看起来模块不完整)。我正在使用(在 Win 上)Python 3.5VStudio 2015SwigWin 3.0.12 【参考方案1】:

在一个愚蠢的错误上花了一些时间〜一小时后:

生成的模块不完整的事实(链接器在不向其提供 MockTest 对象时没有抱怨未定义的符号) 最后生成的是因为我忘记在 MockTest.h 的类成员之前添加public: ... X:((((

又调查了真正的问题,我终于得出一个结论:问题不是因为C++语言(至少不是直接),而是出在中介Swig 创建的层。

详情

MockTest 类(我们尝试将其mockInput 方法返回值patch)在模块MockTest 中定义(!!!即MockTest.py !!!)由 Swig 自动生成(在我的例子中,命令是swig -o MockTest_wrap.cpp -python -c++ MockTest.i)。这是它的定义:

class MockTest(_object):
    __swig_setmethods__ = 
    __setattr__ = lambda self, name, value: _swig_setattr(self, MockTest, name, value)
    __swig_getmethods__ = 
    __getattr__ = lambda self, name: _swig_getattr(self, MockTest, name)
    __repr__ = _swig_repr

    def __init__(self):
        this = _MockTest.new_MockTest()
        try:
            self.this.append(this)
        except __builtin__.Exception:
            self.this = this
    __swig_destroy__ = _MockTest.delete_MockTest
    __del__ = lambda self: None

    def getInput(self):
        return _MockTest.MockTest_getInput(self)

    def mockInput(self, input):
        return _MockTest.MockTest_mockInput(self, input)

您可能已经猜到了,this mockInputpatched,而不是来自 C++ 的那个(它的名字是 MockTest_mockInput,它是由本机模块:_MockTest - 本机名称是在 MocTest_wrap.cpp 中定义的 _wrap_MockTest_mockInput)。当然,由于getInput 调用MockTest_getInputpatching mockInputPython 包装器)没有任何效果(就像您将其主体修改为:return "123" )。

这是我准备的一些示例代码,以更好地说明行为(正如我在评论中提到的,我使用的是 Python 3.5.4)。请忽略最后 3 行代码并输出,直到您阅读下一段:

from unittest.mock import patch
import MockTest


if __name__ == "__main__":
    return_value = "value overridden by `patch`"
    mock_input_arg = "argument passed to `MockTest.mockInput`"
    mock_test = MockTest.MockTest()
    with patch("MockTest.MockTest.mockInput", return_value=return_value):
        print("`mock_test.getInput()` returned: \"\"".format(mock_test.getInput()))
        print("`mock_test.mockInput(\"\")` returned: \"\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))

    print("\nDon't mind the following output for now...\n")  # SAME THING about the following code

    with patch("MockTest._MockTest.MockTest_mockInput", return_value=return_value):
        print("`mock_test.getInput()` returned: \"\"".format(mock_test.getInput()))
        print("`mock_test.mockInput(\"\")` returned: \"\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))

还有输出:

c:\Work\Dev\***\q45934545>"c:\Install\x64\Python\Python\3.5\python.exe" dummy.py
Enter you name
`mock_test.getInput()` returned: "Hello hi person"
`mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`"

Don't mind the following output for now...

Enter you name:
`mock_test.getInput()` returned: "Hello hi person"
`mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`"

然后我进一步尝试patchMockTest._MockTest.MockTest_mockInput,但我得到了相同的输出,因为我没有patchMockTest::mockInput(来自MockTest.cpp)而是@987654346 @(来自 MocTest_wrap.cpp)。

下面是mockInputC++ 代码和Python 之间所有层的表格(getInput 完全相同):

getInput 调用mockInputlayer 0)时,patch 应该发生在哪里,但不幸的是,Python 无法使用。

我想到的第一个st解决方案是直接patching getInput(第1层)(如果在很多地方使用的话)。

【讨论】:

你可以创建你的 c++ 源代码,这样你就可以用另一个流替换 std::cin,你可以从 Python 控制它 感谢@JensMunk 的建议,此时我不确定我知道该怎么做;如果需要,我一定会探索这种方法!

以上是关于使用python包装的c ++ SWIG模拟输入的主要内容,如果未能解决你的问题,请参考以下文章

swig -c++ 选项破坏纯 C 文件的包装,错误 C3861:unresolved externals

SWIG包装器未声明(此功能首次使用)

通过 SWIG 将 Python 3 个字节输入到 C char*

SWIG 在 Windows 中生成 C++ Python3 包装器导致断言 MSVC 2017

包装 std::vector 的 std::vectors,C++ SWIG Python

SWIG 包装器未声明(在此函数中首次使用)