如何测试或模拟“if __name__ == '__main__””内容
假设我有一个具有以下内容的模块:
def main():
pass
if __name__ == "__main__":
main()
我想为下半部分编写一个单元测试(我想实现 100% 的覆盖率)。我发现了 runpy 内置模块执行 import/__name__
设置机制,但我不知道如何模拟或以其他方式检查 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()
Say I have a module with the following:
def main():
pass
if __name__ == "__main__":
main()
I want to write a unit test for the bottom half (I'd like to achieve 100% coverage). I discovered the runpy builtin module that performs the import/__name__
-setting mechanism, but I can't figure out how to mock or otherwise check that the main() function is called.
This is what I've tried so far:
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()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
我不想排除有问题的行,因此基于 这个解决方案的解释,我实现了此处给出的替代答案的简化版本...
if __name__ == "__main__":
在函数中使其易于测试,然后调用该函数以保留逻辑:unittest 模拟了
来获取有问题的行:__name__
。模拟如果您将参数发送到模拟函数中,就像这样,
那么您可以使用
assert_used_once_with()
进行更好的测试:如果需要,您还可以将
return_value
添加到MagicMock()
中,如下所示:I did not want to exclude the lines in question, so based on this explanation of a solution, I implemented a simplified version of the alternate answer given here...
if __name__ == "__main__":
in a function to make it easily testable, and then called that function to retain logic:__name__
usingunittest.mock
to get at the lines in question:If you are sending arguments into the mocked function, like so,
then you can use
assert_called_once_with()
for an even better test:If desired, you can also add a
return_value
to theMagicMock()
like so:如果只是为了获得 100% 并且没有任何“真实”的东西可以测试,那么更容易忽略该行。
如果您使用常规覆盖率库,则只需添加一个简单的注释,该行将在覆盖率报告中被忽略。
https://coverage.readthedocs.io/en/coverage-4.3.3 /exclusion.html
@Taylor Edmiston 的另一条评论也提到了它
If it's just to get the 100% and there is nothing "real" to test there, it is easier to ignore that line.
If you are using the regular coverage lib, you can just add a simple comment, and the line will be ignored in the coverage report.
https://coverage.readthedocs.io/en/coverage-4.3.3/excluding.html
Another comment by @ Taylor Edmiston also mentions it
一种方法是将模块作为脚本运行(例如 os.system(...))并将其 stdout 和 stderr 输出与预期值进行比较。
One approach is to run the modules as scripts (e.g. os.system(...)) and compare their stdout and stderr output to expected values.
我发现这个解决方案很有帮助。如果您使用函数来保留所有脚本代码,则效果很好。
该代码将作为一行代码进行处理。整行是否针对覆盖率计数器执行并不重要(尽管这并不是您实际期望的 100% 覆盖率)
这个技巧也被 pylint 所接受。 ;-)
I found this solution helpful. Works well if you use a function to keep all your script code.
The code will be handled as one code line. It doesn't matter if the entire line was executed for coverage counter (though this is not what you would actually actually expect by 100% coverage)
The trick is also accepted pylint. ;-)
你的代码对我来说似乎大部分是正确的。由于我没有找到适合我的用例的答案,因此我在这里总结我的发现。我想要一些稍微不同的东西:使用 pytest 来测试也接受参数的脚本。
下面的代码应该适用于 python 3.1+,它使用
pytest
和runpy
,并使用unittest
来传递参数。我没有使用mock
因为我还不熟悉它。测试脚本
输入脚本
totest.py
Pytest 脚本
test_totest.py
带参数的 Pytest 脚本
如果您使用
argparse
或sys,这很有用脚本中的 .argv
:测试模块
我也将其包含在此处以更直接地回答问题。
模块脚本
totest/__main__.py
Pytest 脚本
test_totest_module.py
这与之前的脚本几乎相同(
run_module
而不是run_path< /code>):
检查
main()
函数是否被调用。在上面的示例中,
main()
函数始终被调用。如果您对此表示怀疑,请随意:if
中调用main()
后添加一个assert False
:您将看到“Hello “显示在 Pytest 的标准输出日志上(这对于我的示例来说有点特定)。assert False
:pytest 会让你的测试失败,证明main
被执行了runpy
返回包含模块全局变量的字典。因此,您可以在main()
函数中更改其中一些模块全局变量,并稍后检查它们是否已更改。您还可以使用此机制在执行 main() 后检查模块或模块全局变量的状态,这对我来说比仅仅检查函数是否被执行更有意义。Here's a full example of the latter approach
totest/__init__.py
totest/__main__.py
Pytest脚本
test_totest_module.py
当然,你也可以定义自己的函数来断言这一点,将其导出到模块全局变量中,并从 pytest 脚本中调用它。
Your code seems mostly correct to me. As I didn't find an answer that fit my use-case, I am summarizing my findings here. I wanted something slightly different: use pytest to test a script that also takes arguments.
The code below should work with python 3.1+, it uses
pytest
andrunpy
, withunittest
for passing arguments. I didn't usemock
as I am not yet familiar with it.Testing a script
Input script
totest.py
Pytest script
test_totest.py
Pytest script with arguments
This is useful if you use
argparse
orsys.argv
in your script:Testing a module
I also include this here to answer the question more directly.
Module script
totest/__main__.py
Pytest script
test_totest_module.py
This is almost the same script as before (
run_module
instead ofrun_path
):Checking that the
main()
function was called.With the above examples, the
main()
function is always called. If you doubt this, feel free to:assert False
after the call tomain()
in theif
: you will see "Hello" displayed on Pytest's stdout log (this is a bit specific to my example).assert False
in the main function: pytest will fail your test, proving thatmain
was executedrunpy
returns the a dictionary containing the module globals. Therefore, you can change some of these module globals in themain()
function and check that they were changed later. You can also use this mechanism to inspect the state of your module or module globals after executingmain()
, which makes more sense to me than just checking if the function was executed.Here's a full example of the latter approach
totest/__init__.py
totest/__main__.py
Pytest script
test_totest_module.py
Of course, you can also define your own function to assert this, export it in the module globals, and call it from the pytest script.
我的解决方案是使用
imp.load_source()
并通过不提供必需的 CLI 参数、提供格式错误的参数、设置来强制在main()
中尽早引发异常路径以找不到所需文件的方式等。然后在您的测试类中,您可以像这样使用此函数:
My solution is to use
imp.load_source()
and force an exception to be raised early inmain()
by not providing a required CLI argument, providing a malformed argument, setting paths in such a way that a required file is not found, etc.Then in your test class you can use this function like this:
要在 pytest 中导入“main”代码以进行测试,您可以像其他函数一样导入主模块,这要归功于本机 importlib 包:
To import your "main" code in pytest in order to test it you can import main module like other functions thanks to native importlib package :
由于 imp 已被弃用,因此这里有一个
importlib
版本,以及命令行参数模拟。在exec_module
之后,foobar将被完全初始化,并且foobar.main已经被执行。As imp is deprecated, here is a
importlib
version, along with commandline arguments mock. After theexec_module
, foobar would have been fully initiallized, and foobar.main has already been exectued.有人欣赏一点横向思维吗?
我希望这能引起笑声。
我个人不太关心覆盖工具。
但我实际上建议今后将其纳入我的项目中。令我烦恼的是,我的应用程序可能会在交付时缺少一对对其功能至关重要的行,但却通过了所有测试。
我欣然接受这并不完美(例如,可以在多行注释中找到匹配的字符串),但恕我直言,这总比没有好。
PS,您还可以免费获得 UTF-8 编码的基本奖励检查...
Anyone appreciate a bit of lateral thinking?
I hope that raises a laugh.
I'm personally not bothered much about coverage tools.
But I'm actually proposing to include this henceforth in my projects. It bugs me that my app could potentially be shipped with a pair of lines missing which are essential to its functioning, but pass all tests.
I readily accept that this ain't perfect (for example, the matching string could be found inside a multi-line comment), but it's better than nothing, IMHO.
PS you also get a basic bonus check on UTF-8 encoding thrown in for free...
我将选择另一种选择,即从覆盖率报告中排除
if __name__ == '__main__'
,当然,只有当您已经有 main() 函数的测试用例时,您才可以这样做你的测试。至于为什么我选择排除而不是为整个脚本编写一个新的测试用例,是因为如果正如我所说,您已经有一个针对您的
main()
函数的测试用例,那么您添加另一个测试用例脚本的测试用例(仅用于 100% 覆盖率)将只是一个重复的测试用例。对于如何排除
if __name__ == '__main__'
,您可以编写一个覆盖率配置文件并在部分报告中添加:有关覆盖率配置文件的更多信息可以找到此处。
希望这能有所帮助。
I will choose another alternative which is to exclude the
if __name__ == '__main__'
from the coverage report , of course you can do that only if you already have a test case for your main() function in your tests.As for why I choose to exclude rather than writing a new test case for the whole script is because if as I stated you already have a test case for your
main()
function the fact that you add an other test case for the script (just for having a 100 % coverage) will be just a duplicated one.For how to exclude the
if __name__ == '__main__'
you can write a coverage configuration file and add in the section report:More info about the coverage configuration file can be found here.
Hope this can help.
您可以使用
imp
模块而不是import
语句来执行此操作。import
语句的问题在于,在您有机会分配给runpy.__name__< 之前,对
'__main__'
的测试会作为 import 语句的一部分运行。 /代码>。例如,您可以像这样使用
imp.load_source()
:第一个参数分配给导入模块的
__name__
。You can do this using the
imp
module rather than theimport
statement. The problem with theimport
statement is that the test for'__main__'
runs as part of the import statement before you get a chance to assign torunpy.__name__
.For example, you could use
imp.load_source()
like so:The first parameter is assigned to
__name__
of the imported module.哇,我参加聚会有点晚了,但我最近遇到了这个问题,我想我想出了一个更好的解决方案,所以这就是......
我正在开发一个包含十几个脚本的模块一切都以这个完全相同的copypasta结尾:
当然,不可怕,但也无法测试。我的解决方案是在我的一个模块中编写一个新函数:
然后将此 gem 放在每个脚本文件的末尾:
从技术上讲,无论您的脚本是作为模块导入还是作为脚本运行,该函数都将无条件运行。不过,这没关系,因为除非脚本作为脚本运行,否则该函数实际上不会执行任何操作。因此,代码覆盖率看到函数运行后会说“是的,100% 代码覆盖率!”同时,我编写了三个测试来覆盖函数本身:
Blam!现在您可以编写一个可测试的
main()
,将其作为脚本调用,拥有 100% 的测试覆盖率,并且不需要忽略覆盖率报告中的任何代码。Whoa, I'm a little late to the party, but I recently ran into this issue and I think I came up with a better solution, so here it is...
I was working on a module that contained a dozen or so scripts all ending with this exact copypasta:
Not horrible, sure, but not testable either. My solution was to write a new function in one of my modules:
and then place this gem at the end of each script file:
Technically, this function will be run unconditionally whether your script was imported as a module or ran as a script. This is ok however because the function doesn't actually do anything unless the script is being ran as a script. So code coverage sees the function runs and says "yes, 100% code coverage!" Meanwhile, I wrote three tests to cover the function itself:
Blam! Now you can write a testable
main()
, invoke it as a script, have 100% test coverage, and not need to ignore any code in your coverage report.Python 3 解决方案:
使用定义自己的小函数的替代解决方案:
您可以使用以下方法进行测试:
Python 3 solution:
Using the alternative solution of defining your own little function:
You can test with: