Test an examples directory for a Python project - python

I have a project for developing a Python package where the structure of the project is similar to the following:
myproject/
├── README.md
├── examples/
│ ├── ex1.py
│ └── ex2.py
├── pyproject.toml
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── adder.py
│ └── divider.py
└── tests/
├── test_adder.py
├── test_divider.py
└── test_examples.py
The project is for developing a Python package named mypackage which is located in the src directory. The package is uploaded to PyPI where users can pip install it. Tests for the package are run with pytest and are located in the tests directory. Examples of using the package are in the examples directory. The examples are just scripts as shown below for ex1.py
"""
Example 1
"""
from mypackage import adder
x = 2.5
y = 8
a = adder(x, y)
print('a is', a)
The purpose of test_examples.py is to test the example files, its contents are shown below:
from examples import ex1
from examples import ex2
def test_ex1():
ex1
def test_ex2():
ex2
When I run pytest in the myproject directory I get the error shown here:
$ cd myproject
$ pytest
platform darwin -- Python 3.10.6, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/gavinw/Desktop/test-examples
collected 2 items / 1 error
================================================================== ERRORS ==================================================================
_________________________________________________ ERROR collecting tests/test_examples.py __________________________________________________
ImportError while importing test module '/Users/gavinw/Desktop/test-examples/tests/test_examples.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/miniconda3/envs/ztest/lib/python3.10/importlib/__init__.py:126: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_examples.py:1: in <module>
from examples import ex1
E ModuleNotFoundError: No module named 'examples'
========================================================= short test summary info ==========================================================
ERROR tests/test_examples.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================= 1 error in 0.05s =============================================================
It looks like pytest is not able to run the files in the examples directory because the location does not allow them to be imported. Any suggestions on how I can test the example files? Should I even use pytest for testing examples or is there a different testing tool for this?

You should ensure that the examples directory contains __init__.py so it can be imported correctly.
If that is not enough, you can use PYTHONPATH:
PYTHONPATH="/path/to/your/code/project/:/path/to/your/code/project/examples" pytest
PYTHONPATH might be tricky, see https://stackoverflow.com/a/4580120/3800552 and https://stackoverflow.com/a/39682723/3800552 for some usage examples.

There are two approaches that fix the problem. The first approach is to run pytest using the following command:
python -m pytest
The second approach is to add the project directory in the pyproject.toml file using the pythonpath setting as shown below. Then just use the pytest command to run the tests.
[tool.pytest.ini_options]
pythonpath = ["."]

Related

unittest directory structure - cannot import src code

I have the following folder structure in my project:
my-project
src/
__init__.py
script.py
test/
__init__.py
test_script.py
Ideally I want to have a separate folder where all the unit tests go. My test_script.py looks something like this:
from src.script import my_object
class TestClass(unittest.TestCase):
def test_script_object(self):
# unit test here
pass
if __name__ == 'main':
unittest.main()
When I try to run the script (using python test_script.py) I get the following error:
Traceback (most recent call last):
File "test_script.py", line 4, in <module>
from src.script import my_object
ModuleNotFoundError: No module named 'src'
I was following the instructions from this other thread, and I even tried appending src to the sys path (which forces me to change how I do imports in the rest of the project). When I'm not trying to append to the sys path, both of my __init__.py files are empty.
I am using python 3.8.
Does anyone have any suggestions? I'm new to unit testing in python, so maybe there is a better structure or other conventions I'm not aware of. Thanks in advance for your help!
Generally, any instructions that have you modifying sys.path in order to run your tests are sending you in the wrong direction. Your testing tool should be able to discover both your tests and your application code without requiring that sort of hackery.
I generally use pytest for running my tests. I would structure your example like this:
my-project/
├── src
│   ├── conftest.py
│   └── myproject
│   ├── __init__.py
│   └── script.py
└── tests
├── __init__.py
└── test_script.py
Assuming that src/myproject/script.py looks like this:
def double(x: int):
return x*2
And tests/test_script.py look like this:
import myproject.script
def test_double():
res = myproject.script.double(2)
assert res == 4
I can run tests from the my-project directory by simply running pytest:
$ pytest
========================================== test session starts ==========================================
platform linux -- Python 3.11.1, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/lars/tmp/python/my-project
collected 1 item
tests/test_script.py . [100%]
=========================================== 1 passed in 0.00s ===========================================
In this setup, the file src/conftest.py is what allows pytest to automatically discover the location of your application code. Alternatively, you could instead specify that in your pyproject.toml file like this:
[tool.pytest.ini_options]
pythonpath = [
"src"
]
pytest also works if you write unittest-style tests as you show in your question; the above behavior would be identical if tests/test_script.py looked like:
import unittest
import myproject.script
class TestScript(unittest.TestCase):
def test_double(self):
res = myproject.script.double(2)
self.assertEqual(res, 4)
if __name__ == '__main__':
unittest.main()
(But I prefer pytest's simpler syntax.)
Possibly useful reading:
Good integration practices
If you really want to use unittest you can use the same directory layout, but you will need to update sys.path for it to run correctly. The easiest way to do that is:
PYTHONPATH=$PWD/src python -m unittest
This will automatically discover all your tests and run them. You can run a single test like this:
PYTHONPATH=$PWD/src python -m tests.test_script
You can avoid needing to modify sys.path if you use a simpler directory layout:
my-project/
├── myproject
│   ├── __init__.py
│   └── script.py
└── tests
├── __init__.py
└── test_script.py
Both pytest and python -m unittest, when run from the my-project directory, will correctly discover and run your tests without requiring any path modifications.

Failed to import python module from different directory

I have this code structure in python3:
- datalake
__init__.py
utils
__init__.py
utils.py
lambdas
__init__.py
my-lambdas.py
- tests
__init__.py
demo.py
All init__.py files are empty.
My problem is how I can import datalake module from tests/demo.py?
I tried from datalake.utils import utils in demo.py but when I run python tests/demo.py from command line, I get this error ModuleNotFoundError: No module named 'datalake'.
If I use this code:
from ..datalake.utils import utils
I will get error ValueError: attempted relative import beyond top-level package.
I also tried to import the module utils from my-lambda.py file which also failed. The code in my-lambda.py is from datalake.utils import utils but I get ModuleNotFoundError: No module named 'datalake' error when run python datalake/lambda/my-lambda.py from command line.
How can I import the module?
When you run a command like python tests/demo.py, the folder you are in does not get added to the PYTHONPATH, the script folder does. So a top-level import like import datalake will fail. To get around this you can run your tests as a module:
Python 2:
python -m tests/demo
Python 3:
python -m tests.demo
and any datalake imports in demo.py will work.
It sounds like what you really want to do is have a folder with tests separate to your main application and run them. For this I recommend py.test, for your case you can read Tests Outside Application Code for how to do it. TL;DR is run your tests from your top level project folder with python -m py.test and it will work.
First of all, my-lambdas.py is not importable with the import statement as hyphens are not valid in Python identifiers. Try to follow PEP-8's naming conventions, such as mylambdas.py.
Otherwise the package structure looks good, and it should be importable as long as you are at the level above datalake/, e.g., if you were in the directory myproject/ below:
myproject
├── datalake
│ ├── __init__.py
│ ├── utils
│ │ ├── __init__.py
│ │ └── utils.py
│ └── lambdas
│ ├── __init__.py
│ └── mylambdas.py
└── tests
├── __init__.py
└── demo.py
Then this should work:
~/myproject$ python -c 'from datalake import utils'
Otherwise, setting the environment variable PYTHONPATH to the path above datalake/ or modifying sys.path are both ways of changing where Python can import from. See the official tutorial on modules for more information.
Also some general advice: I've found it useful to stick with simple modules rather than packages (directories) until there is a need to expand. Then you can change foo.py into a foo/ directory with an __init__.py file and import foo will work as before, although you may need to add some imports to the __init__.py to maintain API compatibility. This would leave you with a simpler structure:
myproject
├── datalake
│ ├── __init__.py
│ ├── utils.py
│ └── lambdas.py
└── tests
├── __init__.py
└── demo.py
You can add the module directory into your sys.path:
import sys
sys.path.append("your/own/modules/folder") # like sys.path.append("../tests")
but this is a one-shot method, which is just valid at this time, the added path is not permanent, it will be eliminated after the code completed execution.
One of the ways to import the file directly instead of using from, like import util
you can try run :
python -m datalake.lambda.my-lambda
follow: https://docs.python.org/3.7/using/cmdline.html#cmdoption-m

How to use a packed python package without installing it

I have a python 3 package with the following structure:
.
├── package
│ └── bin
└── main_module
│ └── lib
│ ├── __init__.py
│ ├── module1.py
│ ├── module2.py
│ └── module3.py
│ └── test
│ ├── test1.py
│ ├── test2.py
│ └── test3.py
│ └── setup.py
Usually, one runs $ python3 setup.py install and all is good. However, I want to use this package on a cluster server, where I don't have write permissions for /usr/lib/. The following solutions came to my mind.
Somehow install the package locally in my user folder.
Modify the package such that it runs without installation.
Ask the IT guys to install the package for me.
I want to avoid 3., so my question is whether 1. is possible and if not, how I have to modify the code (particularly the imports) in order to be able to use the package without installation. I have been reading about relative imports in python all morning and I am now even more confused than before. I added __init__.py to package and bin and from what I read I assumed it has to be from package.lib import module1, but I always get ImportError: No module named lib.
In order for Python to be able to find your modules you need to add the path of your package to sys.path list. As a general way you can use following snippet:
from sys import path as syspath
from os import path as ospath
syspath.append(ospath.join(ospath.expanduser("~"), 'package_path_from_home'))
os.path.expanduser("~") will give you the path of the home directory and you can join it with the path of your package using os.path.join and then append the final path to sys.path.
If package is in home directory you can just add the following at the leading of your python code that is supposed to use this package:
syspath.append(ospath.join(ospath.expanduser("~"), 'package'))
Also make sure that you have an __init__.py in all your modules.
I had the same problem. I used the first approach
install the package locally in my user folder by running
python setup.py install --user
This will install your module in ~/.local/lib/python3/
Just add the path of your 'package' to environment variable PYTHONPATH. This will get rid of the error you are getting.
OR
programmatically add path of the package to sys.path.append()
you can add this to the "main file" of the package
import sys, os
sys.path.append(os.path.dirname(__file__) + "/..")
you can find the "main file" by looking for this pattern
if __name__ == "__main__":
some_function()

Recursive unittest discover

I have a package with a directory "tests" in which I'm storing my unit tests. My package looks like:
.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   └── test_employee.py
│   └── test_tc.py
└── todo.txt
From my package directory, I want to be able to find both tests/test_tc.py and tests/db/test_employee.py. I'd prefer not to have to install a third-party library (nose or etc) or have to manually build a TestSuite to run this in.
Surely there's a way to tell unittest discover not to stop looking once it's found a test? python -m unittest discover -s tests will find tests/test_tc.py and python -m unittest discover -s tests/db will find tests/db/test_employee.py. Isn't there a way to find both?
In doing a bit of digging, it seems that as long as deeper modules remain importable, they'll be discovered via python -m unittest discover. The solution, then, was simply to add a __init__.py file to each directory to make them packages.
.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   ├── __init__.py # NEW
│   │   └── test_employee.py
│   ├── __init__.py # NEW
│   └── test_tc.py
└── todo.txt
So long as each directory has an __init__.py, python -m unittest discover can import the relevant test_* module.
If you're okay with adding a __init__.py file inside tests, you can put a load_tests function there that will handle discovery for you.
If a test package name (directory with __init__.py) matches the
pattern then the package will be checked for a 'load_tests' function. If
this exists then it will be called with loader, tests, pattern.
If load_tests exists then discovery does not recurse into the package,
load_tests is responsible for loading all tests in the package.
I'm far from confident that this is the best way, but one way to write that function would be:
import os
import pkgutil
import inspect
import unittest
# Add *all* subdirectories to this module's path
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for memname, memobj in inspect.getmembers(mod):
if inspect.isclass(memobj):
if issubclass(memobj, unittest.TestCase):
print("Found TestCase: {}".format(memobj))
for test in loader.loadTestsFromTestCase(memobj):
print(" Found Test: {}".format(test))
suite.addTest(test)
print("=" * 70)
return suite
Pretty ugly, I agree.
First you add all subdirectories to the test packages's path (Docs).
Then, you use pkgutil to walk the path, looking for packages or modules.
When it finds one, it then checks the module members to see whether they're classes, and if they're classes, whether they're subclasses of unittest.TestCase. If they are, the tests inside the classes are loaded into the test suite.
So now, from inside your project root, you can type
python -m unittest discover -p tests
Using the -p pattern switch. If all goes well, you'll see what I saw, which is something like:
Found TestCase: <class 'test_tc.TestCase'>
Found Test: testBar (test_tc.TestCase)
Found Test: testFoo (test_tc.TestCase)
Found TestCase: <class 'test_employee.TestCase'>
Found Test: testBar (test_employee.TestCase)
Found Test: testFoo (test_employee.TestCase)
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Which is what was expected, each of my two example files contained two tests, testFoo and testBar each.
Edit: After some more digging, it looks like you could specify this function as:
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for test in loader.loadTestsFromModule(mod):
print("Found Tests: {}".format(test._tests))
suite.addTests(test)
This uses the loader.loadTestsFromModule() method instead of the loader.loadTestsFromTestCase() method I used above. It still modifies the tests package path and walks it looking for modules, which I think is the key here.
The output looks a bit different now, since we're adding a found testsuite at a time to our main testsuite suite:
python -m unittest discover -p tests
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>]
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>]
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
But we still get the 4 tests we expected, in both classes, in both subdirectories.
The point using init.py, is that one may encounters side effects, like file not being the script file path. Using FOR DOS command can help (not found of DOS commands, but sometimes it helps
setlocal
set CWD=%CD%
FOR /R %%T in (*_test.py) do (
CD %%~pT
python %%T
)
CD %CWD%
endlocal
/R allows for walkthrough the hierarchy from current folder.
(expr) allows for selecting test files (I use _test.py)
%%~pT is $(dirname $T) in shell.
I saved and restore my original directory, as the .bat leaves me where it ends
setlocal ... endlocal to not pollute my environment with CWD.

GAE "Ran 0 tests" from unittest2

I'm attempting to implement unit tests as per GAE's Python SDK Setting up a testing framework section.
Using their sample test runner and test, I receive the following output:
Ran 0 tests in 0.000s
OK
I have unittest2 installed and I also tried running the following commands, but all yielded the same response:
python -m unittest discover ~/foldername
python -m unittest2 discover ~/foldername
I tried adding the Basic example test file from Python docs, but also got the same result.
Tried all of above with and without an __init__.py in the unit test's folder.
What should I try next to debug this issue?
Update
My directory structure is as below:
├── app.yaml
├── myclass.py
├── bulkloader.yaml
├── myappname.py
├── index.yaml
├── testmodel.py
├── test_runner.py
└── unit_tests
   ├── demotestcase.py
   └── sample.py
With the contents of both tests from the above referenced links.
Usage: python -m unittest discover [options]
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)

Categories