4

I have the following code in test.py:

import click

@click.command()
@click.option('--text', default='hello world', help='Text to display.')
def say(text):

    print(text)


if __name__ == "__main__":

    say()

If I call this in the command line, it works:

python test.py --text=hi!
>>hi!

If I want to test my code, I would use:

from click.testing import CliRunner


runner = CliRunner()
result = runner.invoke(test.say, ['--text=blablabla'])

assert result.output == 'blablabla

This works too.

However, if I run my test through coverage.py, I see that the code under if __name__ == "__main__": is not tested. Is there a way to achieve that?

Nicolas Berthier
  • 459
  • 1
  • 8
  • 17
  • 6
    What's the point? Why would you want to test that condition? You should really ask yourself what you're trying to achieve by improving test coverage. – yyny Mar 20 '19 at 20:56
  • 1
    @yoyoyonny, I agree with you, just being a bit anal with 100% test code coverage. And this question picked my curiosity. – Nicolas Berthier Mar 20 '19 at 20:57
  • 1
    Well, the simplest way to get 100% is to remove `if __name__ == "__main__"` code – R2RT Mar 20 '19 at 21:14
  • 1
    or add comment `# pragma: no cover` for these lines of code. If you 100% confident, of course :) – hotenov Apr 24 '21 at 08:19

3 Answers3

3

This is what the standard library module runpy is for. See: https://docs.python.org/3/library/runpy.html#runpy.run_module

For example, if you have a module like foo.bar with bar.py as follows:

BAZ = "QUUX"

if __name__ == '__main__':
    BAZ = "ZIP"

Then you can write a test like:

import runpy

def test_bar():
    assert runpy.run_module('foo.bar')['BAZ'] == 'ZIP'

Or if bar.py is a standalone file, you can write a test like:

import runpy

def test_bar():
    assert runpy.run_path('bar.py')['BAZ'] == 'ZIP'
Max Gasner
  • 1,191
  • 1
  • 12
  • 18
1

Maybe you didn't realize that the codes below __name__ == "__main__" was never invoked by your test code

result = runner.invoke(test.say, ['--text=blablabla'])

Even if you modify the "test.py" like this, your test will not complain.

if __name__ == "__main__":
    raise RuntimeError("Something is wrong")
    say()

The reason is that __name__ == "__main__" will be false if the module file "test.py" is imported. So any codes inside the if clause will be ignored.

To get 100% coverage, run the command directly

$ coverage run test.py --text=blablabla
  blablabla
$ coverage report
  Name      Stmts   Miss  Cover                                                                                                             
  -----------------------------                                                                                                             
  test.py       6      0   100%  
gdlmx
  • 6,479
  • 1
  • 21
  • 39
-2

__name__ != "__main__" if you're calling this from a different module.

What should work is:

import click

@click.command()
@click.option('--text', default='hello world', help='Text to display.')
def say(text):

    print(text)

say()

See this other SO answer if you don't want to do it this way.: https://stackoverflow.com/a/5850364/10813463

Nick Vitha
  • 466
  • 2
  • 9
  • 4
    This doesn't answer the question, which is about "if I run my test through coverage.py, I see that the code under `if __name__ == "__main__":` is not tested. Is there a way to achieve that?". – gdlmx Mar 20 '19 at 21:05