pytest fails when tested function is Click command - python

Using Python 3.6.4, Click==7.0, and pytest==4.4.0.
I got into a trouble when using Click & pytest at the same time.
test_foo.py
import unittest
import click
import pytest
#click.command()
def foo():
print(1)
class TestFoo(unittest.TestCase):
def test_foo(self):
foo()
And when execute pytest test_foo.py::TestFoo::test_foo , it says
Usage: pytest [OPTIONS]
Try "pytest --help" for help.
Error: Got unexpected extra argument
(tests/test_foo.py::TestFoo::test_foo)
All of the pytest options (such as -k or -m) does not work when Click command is enabled to the tested method.
It works fine when I comment out the line of #click.command(), of course.
How does everyone solve this when using both of them at the same time?

You should use the ClickRunner to isolate the execution of click commands in tests. Your example, reworked:
import unittest
import click
import click.testing
#click.command()
def foo():
print(1)
class TestFoo(unittest.TestCase):
def test_foo(self):
runner = click.testing.CliRunner()
result = runner.invoke(foo)
assert result.exit_code == 0
assert result.output == '1\n'
Check out the relevant doc page for more examples.

Related

Monkeypatch an executable in Python pytest

In Python 3.10, I have a function like:
from shutil import which
def my_func():
if which('myexecutable.sh'):
# do stuff
else:
# do other stuff
I would like to write a unit test with Pytest that runs the first part code even though the executable is not present. What is the best way to do this?
I know that I can use monkeypatch.setenv() to set an environment variable, but that's not going to make the which() check pass. There's also the added challenge of making sure this is compatible on Windows and Linux.
You could try like this:
# in script file
from shutil import which
def myfunc():
if which("myexecutable.sh"):
return "OK"
else:
...
# in test file
import pytest
from script import myfunc
#pytest.fixture
def which(mocker):
return mocker.patch("script.which", autospec=True)
def test_myfunc(which):
assert myfunc() == "OK"
Running pytest outputs: 1 passed

Run a single test from within module main

Suppose I have myclass_test.py with Nose tests with following lines:
import nose
class TestMyClass:
def test_method(self):
assert true
def test_another_method(self):
assert true
if __name__ == "__main__":
nose.runmodule()
So I can run python myclass_test.py and have all of my tests to pass. Everything work as expected.
What should I pass to runmodule() to run a single test (for example test_method)?
If it is impossible with Nose is it possible with some other unit-test framework on the same manner (with runner inside module)?
P.S. I actually run not python myclass_test.py but sage myclass_test.sage. These are SageMath interpreter and SageMath file. SageMath code is basically Python code and myclass_test.sage is finally preparsing to myclass_test.sage.py and run with embedded to Sage Python interpreter.
So it isn't recognizable as module by Python interpreter so passing something like sage myclass_test.sage TestMyClass.test_method or sage myclass_test.sage myclass_test.sage:TestMyClass.test_method or sage myclass_test.sage myclass_test.sage.py:TestMyClass.test_method isn't working (by the same reason one cannot directly import sage file as module). Even though Nose passing argv to runmodule() automatically.
P.P.S. Also I cannot use external runner like nosetests -q -s sage myclass_test.sage:TestMyClass.test_method.
I haven't used nose, but here is a possibility to do it with unittest:
import unittest
class TestMyClass(unittest.TestCase):
def test_method(self):
assert True
def test_another_method(self):
assert True
if __name__ == '__main__':
suite = unittest.TestSuite([TestMyClass('test_method')])
unittest.TextTestRunner().run(suite)
And here is the same with pytest:
import pytest
class TestMyClass:
def test_method(self):
assert True
def test_another_method(self):
assert True
if __name__ == '__main__':
pytest.main([f'{__file__}::TestMyClass::test_method'])
Ok, I also checked with nose, and this seems to work:
import nose
from nose.suite import ContextSuite
class TestMyClass:
def test_method(self):
assert True
def test_another_method(self):
assert True
if __name__ == '__main__':
suite = ContextSuite([TestMyClass.test_method])
nose.runmodule(suite=suite)
though the output is strange - while it certainly runs the test, it says "Ran 0 tests".

Recognizing module level tests

How can I get test_greet to run in the below; note: test_one(when uncommented) is seen and run by the test runner; to be specific, I want the line unittest.main() to correctly pick up the module level test (test_greet).
import unittest
#class MyTests(unittest.TestCase):
# def test_one(self):
# assert 1==2
def test_greet():
assert 1==3
if __name__=="__main__":
unittest.main()
Let's say i have a file called MyTests.py as below:
import unittest
class MyTests(unittest.TestCase):
def test_greet(self):
self.assertEqual(1,3)
Then:
Open a CMD in the folder that MyTests.py exists
Run python -m unittest MyTests
Please note that, all your tests must have the test_ otherwise, it will not be run.

pytest: run test from code, not from command line

Is it possible to run tests from code using pytest? I did find pytest.main, but it's just a command line interface available from code. I would like to pass a test class / function from the code.
In unittest it's possible this way:
from unittest import TestLoader, TestCase, TestResult
class TestMy(TestCase):
def test_silly(self):
assert False
runner = TestLoader()
test_suite = runner.loadTestsFromTestCase(TestMy)
test_result = TestResult()
test_suite.run(test_result)
print(test_result)
Yes it's possible, that way for instance:
from pytest import main
class TestMy:
def test_silly(self):
assert False
main(['{}::{}'.format(__file__, TestMy.__name__)])
You can pass any argument to main as if called from command line.

Passing command line arguments in python by pytest

I am able to pass command line arguments when running
python <filename>.py arg1
But when am trying to pass the command line arguments for running pytest it fails and gives error as below. Can you please advise.
pytest <filename>.py arg1
ERROR: file not found: arg1
EDIT:
For example am thinking of using it this way assuming I have passed an argument and am reading it via sys.argv:
import sys
arg = sys.argv[3]
def f():
return 3
def test_function():
assert f() == arg
Your pytest <filename>.py arg1 command is trying to call pytest on two modules <filename>.py and arg1 , But there is no module arg1.
If you want to pass some argument before running pytest then run the pytest from a python script after extracting your variable.
As others suggested though you would probably want to parameterize your tests in some other way, Try:Parameterized pytest.
# run.py
import pytest
import sys
def main():
# extract your arg here
print('Extracted arg is ==> %s' % sys.argv[2])
pytest.main([sys.argv[1]])
if __name__ == '__main__':
main()
call this using python run.py filename.py arg1
Here's the method I just cooked up from reading the parameterized pytest docs and hacking for a while... I don't know how stable or good it is going to be overall since I just got it working.
I did however check that HTML coverage generation works with this method.
add a file to your test directory for configuring the command-line args you want to pass:
tests\conftest.py
# this is just so we can pass --server and --port from the pytest command-line
def pytest_addoption(parser):
''' attaches optional cmd-line args to the pytest machinery '''
parser.addoption("--server", action="append", default=[], help="real server hostname/IP")
parser.addoption("--port", action="append", default=[], help="real server port number")
and then add a test file, with this special pytest_generate_tests function which is called when collecting a test function
tests\test_junk.py
def pytest_generate_tests(metafunc):
''' just to attach the cmd-line args to a test-class that needs them '''
server_from_cmd_line = metafunc.config.getoption("server")
port_from_cmd_line = metafunc.config.getoption("port")
print('command line passed for --server ({})'.format(server_from_cmd_line))
print('command line passed for --port ({})'.format(port_from_cmd_line))
# check if this function is in a test-class that needs the cmd-line args
if server_from_cmd_line and port_from_cmd_line and hasattr(metafunc.cls, 'real_server'):
# now set the cmd-line args to the test class
metafunc.cls.real_server = server_from_cmd_line[0]
metafunc.cls.real_port = int(port_from_cmd_line[0])
class TestServerCode(object):
''' test-class that might benefit from optional cmd-line args '''
real_server=None
real_port = None
def test_valid_string(self):
assert self.real_server!=None
assert self.real_port!=None
def test_other(self):
from mypackage import my_server_code
if self.real_server != None:
assert "couldn\'t find host" not in my_server_code.version(self.real_server, self.real_port)
then run (with HTML coverage, for example) with:
pytest tests\test_junk.py --server="abc" --port=123 --cov-report html --cov=mypackage
It seems monkeypatch also works.
Example:
import sys
def test_example(monkeypatch):
monkeypatch.setattr(sys, 'argv', ['/path/to/binary', 'opt1', '...'])
assert f() == '...'
def test_another():
# sys.argv is not modified here
assert f() != '...'

Categories