Is it possible to use parameterize imports with pytest? - python

I am using pytest to test a project I am working on. I have a project structure like
|my_project
||__init__.py
||my_code.py
||test.py
and test.py looks like
# contents of test.py
import .my_code
def test_function():
...
...
To run the tests, I can just run python -m pytest from this directory. So far so good.
But for the code to run remotely, I have to use Pants to build a virtual environment so that the import statement will actually look like:
import long.path.to.my_project.my_code as my_code
I want to make sure that the code still works in this virtual environment as well, so right now I have a different file called test_venv.py with identical tests, where the only difference is the import.
# contents of test_venv.py
import long.path.to.my_project.my_code as my_code
def test_function():
...
...
This does work, but it's very annoying to have two almost identical test files. Is there a way to make the import statement parameterized, so that I can tell pytest where I want to import from when I run the test?

You can combine imports. Do one way, catch exception, do the other way.
try:
import .my_code
except ImportError:
import long.path.to.my_project.my_code as my_code
def test_function():
...
...
I'm not sure, may be it's should be
try:
import long.path.to.my_project.my_code as my_code
except ImportError:
import .my_code

After playing around with #morhc's suggestion to use this, I figured out a way. It involves using parameterized fixtures and importlib. I set up the fixture as follows.
#pytest.fixture(scope='module', params=['local', 'installed'])
def my_code_module(request):
if request.param == 'local':
return importlib.import_module("my_code")
if request.param == 'installed':
return importlib.import_module("long.path.to.my_project.my_code")
Then write the tests to request the fixture as follows.
def test_code(my_code_module):
assert my_code_module.whatever() == ...

Related

Avoiding running top-level module code in unit test

I am attempting to unit test some Python 3 code that imports a module. Unfortunately, the way the module is written, simply importing it has unpleasant side effects, which are not important for the tests. I'm trying to use unitest.mock.patch to get around it, but not having much luck.
Here is the structure of an illustrative sample:
.
└── work
├── __init__.py
├── test_work.py
├── work.py
└── work_caller.py
__init__.py is an empty file
work.py
import os
def work_on():
path = os.getcwd()
print(f"Working on {path}")
return path
def unpleasant_side_effect():
print("I am an unpleasant side effect of importing this module")
# Note that this is called simply by importing this file
unpleasant_side_effect()
work_caller.py
from work.work import work_on
class WorkCaller:
def call_work(self):
# Do important stuff that I want to test here
# This call I don't care about in the test, but it needs to be called
work_on()
test_work.py
from unittest import TestCase, mock
from work.work_caller import WorkCaller
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
with mock.patch("work.work.unpleasant_side_effect") as mocked_function:
sut = WorkCaller()
sut.call_work()
In work_caller.py I only want to test the beginning code, not the call to work_on(). When I run the test, I get the following output:
paul-> python -m unittest
I am an unpleasant side effect of importing this module
Working on /Users/paul/src/patch-test
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
I was expecting that the line I am an unpleasant side effect of importing this module would not be printed because the function unpleasant_side_effect would be mocked. Where might I be going wrong?
The unpleasant_side_effect is run for two reasons. First because the imports are handled before the test case is started and is therefore not mocked when importing is happening. Secondly, because the mocking itself imports work.py and thus runs unpleasant_side_effect even if work_caller.py was not imported.
The import problem can be solved by mocking the module work.py itself. This can either be done globally in the test module or in the testcase itself. Here I assigned it a MagicMock, which can be imported, called etc.
test_work.py
from unittest import TestCase, mock
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
import sys
sys.modules['work.work'] = mock.MagicMock()
from work.work_caller import WorkCaller
sut = WorkCaller()
sut.call_work()
The downside is that work_on is also mocked, which I am not sure whether is a problem in your case.
It is not possible to not run the entire module when it is imported, since functions and classes are also statements, thus the module execution has to finish before returning to the caller, where one want to alter the imported module.
In case you asked partially about the best practice.
You should always split your code to library used by every other code and side-effect lines. And probably eliminate side-effects by calling the side-effecting code from you def main(): But if you want to keep side-effects anyway, then you could do:
work_lib.py:
...no side-effects...
work.py
from work_lib import ...
...side-effects....
test_work.py
from work_lib import ...
Another solution is to put this line ahead of any code that you don't want to run on import:
if __name__ == "__main__":
If the code is at the highest/outermost level of a module, the name will be "main" when running directly or will be the module name when being imported. So in your example, if you put that line ahead of your call to unpleasant_side_effect(), the function wouldn't get called when the module is imported.

How can I integrate doctests with unittest's test discovery?

I wrote a python script to do all my tests automatically for me, and generate a HTML report. I discovered discover for unittests the other day which lets me run all the unittests in a given directory without explicitly naming them, and I'd really like to be able to do my doctests the same way, rather than having to import each module explicitly.
I found some info on how to do this at https://docs.python.org/2/library/doctest.html but didn't really get it. Could you please help me with using discover with my doctests?
Python test discovery with doctests, coverage and parallelism is related, but still doesn't answer my question.
coverage_module
import coverage
import doctest
import unittest
import os
# import test_module
import my_module
cov = coverage.Coverage()
cov.start()
# running doctest by explicity naming the module
doctest.testmod(my_module)
# running unittests by just specifying the folder to look into
testLoad = unittest.TestLoader()
testSuite = testLoad.discover(start_dir=os.getcwd())
runner = unittest.TextTestRunner()
runner.run(testSuite)
cov.stop()
cov.save()
cov.html_report()
print "tests completed"
test_module
import unittest
import doctest
from my_module import My_Class
class My_Class_Tests(unittest.TestCase):
def setUp(self):
# setup variables
def test_1(self):
# test code
# The bit that should load up the doctests? What's loader, tests, and ignore though?
# Is this in the right place?
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(module_with_doctests))
return tests
if __name__ == '__main__':
unittest.main()
Lets figure out what's happening there
1) unittest.discovery
It has no clue of doctests as doctests is a different framework.
So unittest isn't supposed to discover doctests out of the box.
That means you'll need to glue them together by hand
2) doctest
It's essentially a separate framework although it has some glueing classes to convert doctests into unittest-like TestCases.
https://docs.python.org/2.7/library/doctest.html#doctest.DocTestSuite
3) discover
Didn't get what discover you mean, I suppose it's
python -m unittest discover
If not and you're talking about https://pypi.python.org/pypi/discover then just forget about it - it's a backport for earlier versions of python
4) what to do
either scatter a lot of load_tests hooks across your code as described here https://docs.python.org/2.7/library/doctest.html#unittest-api or code a method to collect all the modules your have in one place and convert them into a DocTestSuite[s] https://docs.python.org/2.7/library/doctest.html#doctest.DocTestSuite
But honestly neither approach makes any sense nowadays as it boils down to:
$ py.test --doctest-modules
or
$ nosetests --with-doctest
Of course coverage and lots of bells & whistles are also supplied by these frameworks and you may keep sticking to unittest.TestCase, and you won't even need to create a coverage_module so I would dig into one of them rather then trying to come up with your own solution

Test if code is executed from within a py.test session

I'd like to connect to a different database if my code is running under py.test. Is there a function to call or an environment variable that I can test that will tell me if I'm running under a py.test session? What's the best way to handle this?
A simpler solution I came to:
import sys
if "pytest" in sys.modules:
...
Pytest runner will always load the pytest module, making it available in sys.modules.
Of course, this solution only works if the code you're trying to test does not use pytest itself.
There's also another way documented in the manual:
https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test-environment-variable
Pytest will set the following environment variable PYTEST_CURRENT_TEST.
Checking the existence of said variable should reliably allow one to detect if code is being executed from within the umbrella of pytest.
import os
if "PYTEST_CURRENT_TEST" in os.environ:
# We are running under pytest, act accordingly...
Note
This method works only when an actual test is being run.
This detection will not work when modules are imported during pytest collection.
A solution came from RTFM, although not in an obvious place. The manual also had an error in code, corrected below.
Detect if running from within a pytest run
Usually it is a bad idea to make application code behave differently
if called from a test. But if you absolutely must find out if your
application code is running from a test you can do something like
this:
# content of conftest.py
def pytest_configure(config):
import sys
sys._called_from_test = True
def pytest_unconfigure(config):
import sys # This was missing from the manual
del sys._called_from_test
and then check for the sys._called_from_test flag:
if hasattr(sys, '_called_from_test'):
# called from within a test run
else:
# called "normally"
accordingly in your application. It’s also a good idea to use your own
application module rather than sys for handling flag.
Working with pytest==4.3.1 the methods above failed, so I just went old school and checked with:
script_name = os.path.basename(sys.argv[0])
if script_name in ['pytest', 'py.test']:
print('Running with pytest!')
While the hack explained in the other answer (http://pytest.org/latest/example/simple.html#detect-if-running-from-within-a-pytest-run) does indeed work, you could probably design the code in such a way you would not need to do this.
If you design the code to take the database to connect to as an argument somehow, via a connection or something else, then you can simply inject a different argument when you're running the tests then when the application drives this. Your code will end up with less global state and more modulare and reusable. So to me it sounds like an example where testing drives you to design the code better.
This could be done by setting an environment variable inside the testing code. For example, given a project
conftest.py
mypkg/
__init__.py
app.py
tests/
test_app.py
In test_app.py you can add
import os
os.environ['PYTEST_RUNNING'] = 'true'
And then you can check inside app.py:
import os
if os.environ.get('PYTEST_RUNNING', '') == 'true':
print('pytest is running')

Unit-testing extensions for an 'external' program with pyunit

I'm struggling to know where to start with unittest, having read the dive-into-python tutorial and looked at http://pyunit.sourceforge.net/.
I've got a piece of analysis software (call it 'prog.exe') which uses python for its input decks. I've started writing a python module which I'm going to import from that input deck to provide some useful functionality. So, running one of these analyses will go like this:
prog.exe inputdeck.py
where inputdeck.py contains:
from mymodule import mystuff
So how do I set up and run tests on mymodule? Should the above be in a system call in a setUp method of the test, or what?
Ok - solution:
Don't use unittest.main() as that's the command line tool. Instead call the appropriate unittest methods directly as follows:
From the command line run:
prog.exe mytests.py
where mytests.py contains:
import unittest
# ... code to run the analysis which we'll use for the tests ...
# ... test definitions ...
suite = unittest.TestLoader().loadTestsFromTestCase(test_cases)
unittest.TextTestRunner().run(suite)
See example at http://docs.python.org/release/2.6.7/library/unittest.html#unittest.TextTestRunner
Pyunit is a little bit outdated (2001), it is now completely included in python core distribution (http://docs.python.org/library/unittest.html). You should start read this documentation, especially the basic example part.
To test your module you'll have to create a file, let's call it mymodule_test.py and put in it something like this :
import unittest
from mymodule import mystuff
class MyTestCase(unittest.TestCase):
def test_01a(self):
""" test mystuff"""
self.failUnless(mystuff.do_the_right_stuff())
if __name__ == '__main__':
unittest.main()
and run it with python mymodule_test.py

unittest in python: ignore an import from the code I want to test

I have a python program that imports pythoncom (and uses pythoncom.CoCreateInstance from it). I want to create a unittest for the program logic without it importing pythoncom (so I can run the test on Linux as well).
What options are there? Can I do it without modifying the system under test?
What I found so far:
sys.modules["pythoncom"] = "test"
import module_that_imports_pythoncom
My problem with it is if I have:
from pythoncom.something import something
I'll get:
ImportError: No module named something.something
And sys.modules["something.something"] or sys.modules["pythoncom.something.something"] doesn't work.
Any ideas?
If you need to run tests and they actually are OS-dependent, you might want to use these decorators for example:
def run_only(func, predicate):
if predicate():
return func
else:
def f(*args, **kwargs): pass
return f
def run_only_for_linux(func):
pred = lambda: sys.platform == 'linux2'
return run_only(func, pred)
#run_only_for_linux
def hello_linux():
"""docstring"""
print("hello linux")
In this way you declare that the test only runs on linux without adding ugly complexity in the test itself.
You could put import pythoncom into a try except block.
Ok, what if you modify PYTHONPATH in the tests, and make a new package on the filesystem in a testing directory called pythoncom with the necessary subdirectories?

Categories

Resources