Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何测试或模拟“if uuuu name uuuuuu=='uuuuu main uuuuuuuuu'”内容_Python_Unit Testing_Testing_Mocking_Python Import - Fatal编程技术网

Python 如何测试或模拟“if uuuu name uuuuuu=='uuuuu main uuuuuuuuu'”内容

Python 如何测试或模拟“if uuuu name uuuuuu=='uuuuu main uuuuuuuuu'”内容,python,unit-testing,testing,mocking,python-import,Python,Unit Testing,Testing,Mocking,Python Import,假设我有一个包含以下内容的模块: def main(): pass if __name__ == "__main__": main() 我想为下半部分编写一个单元测试,我希望实现100%的覆盖率。我发现了执行import/\uu name\uuu-设置机制的runpy内置模块,但我不知道如何模拟或以其他方式检查是否调用了main函数 这就是我迄今为止所尝试的: import runpy import mock @mock.patch('foobar.main') def t

假设我有一个包含以下内容的模块:

def main():
    pass

if __name__ == "__main__":
    main()
我想为下半部分编写一个单元测试,我希望实现100%的覆盖率。我发现了执行import/\uu name\uuu-设置机制的runpy内置模块,但我不知道如何模拟或以其他方式检查是否调用了main函数

这就是我迄今为止所尝试的:

import runpy
import mock

@mock.patch('foobar.main')
def test_main(self, main):
    runpy.run_module('foobar', run_name='__main__')
    main.assert_called_once_with()
您可以使用imp模块而不是import语句来执行此操作。import语句的问题在于,“\uuuuu main\uuuuuuuuu”的测试在您有机会分配给runpy.\uuuuuuuu name\uuuuuuuuu之前作为import语句的一部分运行

例如,您可以使用imp.load_源,如下所示:


第一个参数被分配给导入模块的_name _。

一种方法是将模块作为脚本运行,例如操作系统。。。并将它们的stdout和stderr输出与预期值进行比较。

我将选择另一种替代方法,即从覆盖率报告中排除if uuu name_uuuu='uu main_uuu',当然,只有在测试中已经有了主函数的测试用例时,才能这样做

至于我为什么选择排除而不是为整个脚本编写一个新的测试用例,是因为如果正如我所说的那样,您已经为您的主函数创建了一个测试用例,那么您为脚本添加另一个测试用例只是为了获得100%的覆盖率,这将是一个重复的测试用例

关于如何排除if _uname _u=='_u main _uu',您可以编写一个覆盖率配置文件并添加到部分报告中:

[report]

exclude_lines =
    if __name__ == .__main__.:
可以找到有关覆盖率配置文件的更多信息


希望这能有所帮助。

哇,我来晚了一点,但我最近遇到了这个问题,我想我想出了一个更好的解决方案,所以这里是

我正在开发一个模块,该模块包含十几个脚本,所有脚本都以以下内容结尾:

if __name__ == '__main__':
    if '--help' in sys.argv or '-h' in sys.argv:
        print(__doc__)
    else:
        sys.exit(main())
当然不可怕,但也不可测试。我的解决方案是在我的一个模块中编写一个新函数:

def run_script(name, doc, main):
    """Act like a script if we were invoked like a script."""
    if name == '__main__':
        if '--help' in sys.argv or '-h' in sys.argv:
            sys.stdout.write(doc)
        else:
            sys.exit(main())
然后将此gem放在每个脚本文件的末尾:

run_script(__name__, __doc__, main)
从技术上讲,无论脚本是作为模块导入还是作为脚本运行,此函数都将无条件运行。但是,这是可以的,因为除非脚本作为脚本运行,否则函数实际上不会执行任何操作。所以代码覆盖率看到函数运行并说是的,100%的代码覆盖率!同时,我编写了三个测试来覆盖函数本身:

@patch('mymodule.utils.sys')
def test_run_script_as_import(self, sysMock):
    """The run_script() func is a NOP when name != __main__."""
    mainMock = Mock()
    sysMock.argv = []
    run_script('some_module', 'docdocdoc', mainMock)
    self.assertEqual(mainMock.mock_calls, [])
    self.assertEqual(sysMock.exit.mock_calls, [])
    self.assertEqual(sysMock.stdout.write.mock_calls, [])

@patch('mymodule.utils.sys')
def test_run_script_as_script(self, sysMock):
    """Invoke main() when run as a script."""
    mainMock = Mock()
    sysMock.argv = []
    run_script('__main__', 'docdocdoc', mainMock)
    mainMock.assert_called_once_with()
    sysMock.exit.assert_called_once_with(mainMock())
    self.assertEqual(sysMock.stdout.write.mock_calls, [])

@patch('mymodule.utils.sys')
def test_run_script_with_help(self, sysMock):
    """Print help when the user asks for help."""
    mainMock = Mock()
    for h in ('-h', '--help'):
        sysMock.argv = [h]
        run_script('__main__', h*5, mainMock)
        self.assertEqual(mainMock.mock_calls, [])
        self.assertEqual(sysMock.exit.mock_calls, [])
        sysMock.stdout.write.assert_called_with(h*5)
该死!现在,您可以编写一个可测试的main,将其作为脚本调用,具有100%的测试覆盖率,并且不需要忽略覆盖率报告中的任何代码。

我的解决方案是使用imp.load\u source,通过不提供所需的CLI参数、提供格式错误的参数,强制在main的早期引发异常,以找不到所需文件的方式设置路径,等等

import imp    
import os
import sys

def mainCond(testObj, srcFilePath, expectedExcType=SystemExit, cliArgsStr=''):
    sys.argv = [os.path.basename(srcFilePath)] + (
        [] if len(cliArgsStr) == 0 else cliArgsStr.split(' '))
    testObj.assertRaises(expectedExcType, imp.load_source, '__main__', srcFilePath)
然后在测试类中,您可以像这样使用此函数:

def testMain(self):
    mainCond(self, 'path/to/main.py', cliArgsStr='-d FailingArg')

我发现这个解决方案很有帮助。如果您使用一个函数来保存所有的脚本代码,那么这个函数会很好地工作。 代码将作为一个代码行处理。是否为覆盖率计数器执行整行并不重要,尽管这并不是您实际期望的100%覆盖率 这个把戏也被派林接受了-

if __name__ == '__main__': \
    main()
Python 3解决方案:

import os
from importlib.machinery import SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
from importlib import reload
from unittest import TestCase
from unittest.mock import MagicMock, patch
    

class TestIfNameEqMain(TestCase):
    def test_name_eq_main(self):
        loader = SourceFileLoader('__main__',
                                  os.path.join(os.path.dirname(os.path.dirname(__file__)),
                                               '__main__.py'))
        with self.assertRaises(SystemExit) as e:
            loader.exec_module(module_from_spec(spec_from_loader(loader.name, loader)))
使用定义自己的小函数的替代解决方案:

# module.py
def main():
    if __name__ == '__main__':
        return 'sweet'
    return 'child of mine'
您可以使用以下工具进行测试:

# Override the `__name__` value in your module to '__main__'
with patch('module_name.__name__', '__main__'):
    import module_name
    self.assertEqual(module_name.main(), 'sweet')

with patch('module_name.__name__', 'anything else'):
    reload(module_name)
    del module_name
    import module_name
    self.assertEqual(module_name.main(), 'child of mine')

我不想排除有问题的行,所以基于,我实现了一个简化版本的

我将if uuu name uuu==\uuuuuu main uuuu:包装在一个函数中,使其易于测试,然后调用该函数以保留逻辑: 我使用unittest.mock模拟了_name __;以获得有问题的行: 如果将参数发送到模拟函数中,如下所示

if __name__ == "__main__":
    main(main_args)
然后,您可以使用名为_once _的assert_进行更好的测试:

expected_args = ["expected_arg_1", "expected_arg_2"]
mock_main.assert_called_once_with(expected_args)
如果需要,还可以向MagicMock添加返回值,如下所示:


在子进程中运行脚本并期望coverage.py跟踪执行的行并不像听起来那么容易,可以在这里找到使此解决方案工作的更多信息:imp模块的工作方式似乎与我在问题中使用的runpy模块非常相似。问题在于,在加载模块之后和运行代码之前,显然无法插入mock。你对此有什么建议吗?呵呵,我已经添加了一个新的答案,它提供了100%的测试覆盖率!不需要忽略任何东西。让我知道你的想法:谢谢。对于那些想知道的人:nose cov在下面使用coverage.py,所以一个包含上述内容的.coverage.c文件就可以了。IMHO,即使我觉得它有趣和有用,这个答案实际上并没有给OP一个响应。他想测试main是否被调用,而不是跳过这个检查。否则,脚本实际上可以做除实际期望之外的所有事情,当启动时,测试说OK,一切正常!。主函数可以进行完整的单元测试,即使从未被实际调用
从onse到OP,但这是一个很好的回答,从实际的角度来看,这就是我发现这个问题的原因。类似的解决方案是使用pragma:no cover,如果uuu name_uuu=='uuuu main_uuuu':pragma:no cover,则与此类似。就我个人而言,我不愿意这样做,因为它会使代码混乱,而且非常难看,所以我认为mouad的答案是最好的解决方案,但其他人可能会发现它很有用。@mouad如果我们非常具体,我认为从技术上讲,正则表达式行应该使用['],而不是。比如:_name_==[']_main_u[']:。我很感激你在寻找解决方案时的创造力和毅力,但如果你在我的团队中,我会否决这种编码方式。Python的优点之一是它非常地道。如果uuu name uuuu==。。。是让一个模块脚本。任何pythonista都会认识到这条线并理解它的作用。你的解决方案就是混淆显而易见的东西,除了搔搔痒之外,没有什么好的理由。正如我所说:一个聪明的解决方案,但聪明并不总是等同于正确。如果您只有一个模块,或者如果每个模块在作为脚本调用时做了一些不同的事情,那么这很好,但正如我所说,我有十几个文件,它们的if _uname _u==。。。最后是块,这是对“不要重复你自己”的严重违反,而且当你需要在很多地方以相同的方式修复bug时,修复bug也很困难。像这样统一逻辑可以提高可测试性,并减少出现bug的可能性。如果您担心人们不理解它,请将函数命名为If_name_equals_main,人们就会知道。如果在If_name_下缩进的块中有任何逻辑。。。那么你就做错了,应该重构。if\uuuu name\uuuuu下的唯一一行代码。。。我不知道我是否同意这个说法。是的,如果你有逻辑,你应该重构。但这并不意味着你能拥有的唯一东西是如果_; name _; _;。。。这是主要的。例如,我喜欢使用argeparse并在if\uuuu name\uuuuuu中构造我的解析器。。。部分然后抽象我的main以使用显式参数,而不是像:mainparser.parse_args这样的东西。这使得在需要时从另一个模块调用main更加容易。否则,您必须构造一个argeparse.Namespace对象并获得所有正确的默认参数。还是有更惯用的方法来做这件事?@MichaelLeonard-我不确定我是否正确理解了你的问题。根据惯例,main是作为脚本调用模块时应该运行的函数,因此它是解析代码的常规位置。如果您希望从模块中公开一个函数,则不应将其称为main,而应称为其他函数,并且main函数应通过解析的参数调用该函数。还是我完全误解了你的问题?
from unittest.mock import patch, MagicMock
from myapp import module

def test_name_equals_main():
  # Arrange
  with patch.object(module, "main", MagicMock()) as mock_main:
    with patch.object(module, "__name__", "__main__"):
         # Act
         module.init()

  # Assert
  mock_main.assert_called_once()
if __name__ == "__main__":
    main(main_args)
expected_args = ["expected_arg_1", "expected_arg_2"]
mock_main.assert_called_once_with(expected_args)
with patch.object(module, "main", MagicMock(return_value='foo')) as mock_main: