42

I made my project with setuptools and I want to test it with tox. I listed dependencies in a variable and added to setup() parameter (tests_require and extras_require). My project needs to install all of the dependencies listed in tests_require to test but pip install is not installing them.

I tried this but it did not work:

install_command = pip install {opts} {packages}[tests]

How can I install test dependencies without having to manage multiple dependency lists (i.e. Having all dependencies listed in both test_requirements.txt and the tests_require variable)?

chown
  • 51,908
  • 16
  • 134
  • 170
item4
  • 785
  • 1
  • 7
  • 11
  • 1
    I'm not sure if you can - all of the projects I've seen with `tox` integration just have a specific test requirements file and set `deps = -r whatever_file.txt` in `tox.ini`. – jonrsharpe Apr 25 '15 at 21:08
  • @jonrsharpe just I want to make no deps text file. If I make it, I must manage two deps list. – item4 Apr 25 '15 at 21:22
  • Yes, I understand why you don't want to do it, and I'm telling you that *I'm not sure you can avoid it*. The other option is to read in your test dependencies from the file in `setup.py` rather than hard-coding them; that takes you back to a single DRY list. – jonrsharpe Apr 25 '15 at 21:29
  • 1
    For anyone coming across this question now: As of 2021, `setup.py test` and `tests_require` are now deprecated ([issue](https://github.com/pypa/setuptools/issues/1684), [docs](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#optional-dependencies)). So the answers involving `extras` below are no longer just workarounds but the only official non-deprecated way of implementing test-only dependencies. – smheidrich Sep 10 '21 at 09:51

4 Answers4

62

I've achieved this by committing a slight abuse of extra requirements. You were almost there trying the extras syntax, just that tests_require deps aren't automatically available that way.

With a setup.py like this:

from setuptools import setup

test_deps = [
    'coverage',
    'pytest',
]
extras = {
    'test': test_deps,
}

setup(
    # Other metadata...
    tests_require=test_deps,
    extras_require=extras,
)

You can then get the test dependencies installed with the extras syntax, e.g. from the project root directory:

$ pip install .[test]

Give that same syntax to Tox in tox.ini, no need to adjust the default install_command:

[testenv]
commands = {posargs:pytest}
deps = .[test]

Now you don't need to maintain the dependency list in two places, and they're expressed where they should be for a published package: in the packaging metadata instead of requirements.txt files.

It seems this little extras hack is not all that uncommon.

Community
  • 1
  • 1
ches
  • 6,382
  • 2
  • 35
  • 32
  • 1
    It seems this does not just install the dependencies, but also the current project. – tkruse Aug 18 '19 at 08:19
  • 1
    @tkruse Yes, that is inherently what `pip install .` does with or without the `[extras]` syntax. I typically find that inconsequential as I'm normally developing in a virtualenv, and even beneficial because your tests can use module imports for your code under test and help catch that you don't have relative import issues, etc. @Guy Gangemi raises a caveat in his answer that may be a valid one, but it's not a scenario I've encountered in practice personally. His answer, based on a newer feature of Tox, may be a good one—I don't want to opine on the setuptools `python setup.py test` debate. – ches Jan 27 '20 at 08:59
17

Solution

Tox 2.6 introduced extras option. This will install extras from the sdist it just built, only for that sdist and at the time it was doing the normal sdist install.

setup.py should look like:

setuptools.setup(
    ...
    extras_require={
        'tests': ['pytest>=3.7.0', 'more_packages'],
    },
    ...
 )

tox.ini should look like:

[testenv]
...
extras = tests
...

Concerns

Other approaches may get similar results but introduce unnecessary risk and limits the usefulness of other features:

deps =.[tests] is a bit of a hack. The field is for packages the environment needs. If setup.py install_requires references another package you develop, you could use it to pull in a pre-release version of it. As shown, it will install your whole package from your working directory (whatever state that is in!) just to get at the list of packages in tests. install_command will run next, installing your newly minted sdist. In short, issues with the sdist may be masked since you already have installed from your working copy.

Editing install_command is overkill. It'll overwrite items installed via deps. (again maybe you used it to install a specific version of a package).

tests_require is used when python setup.py test is run. Tox recommends avoiding python setup.py test so you can ignore tests_require all together.

Guy Gangemi
  • 1,533
  • 1
  • 13
  • 25
  • If `python setup.py test` is discouraged and so is `pip install .[test]` then what is the utility of defining these tests as a `setuptools` extra at all? Shouldn't we just keep the Tox definition completely separate from `setup.py`? – Robin Winslow Apr 26 '23 at 14:58
  • @RobinWinslow we agree. `pip install [test]` installs packages for running tests probably though pytest or the likes. A user has the option of running your tests in the environment they intend to run your package in. It should work independently of Tox. A dev uses Tox to run those tests under multiple environments and the example `.ini` shown is the best way to make that `pip install` call with minimal side effects. – Guy Gangemi Apr 28 '23 at 00:55
4

What you can do is have a single file (called test_requirements.txt) and list out the test dependencies like so:

dnspython==1.12.0
easydev==0.8.3
enum34==1.0.4
fabric==1.10.1
...

Then, in setup.py, parse and store the file contents in a list and pass that list to setup:

tests_require = [line.strip() for line in 
                 open('test_requirements.txt')
                 if line.strip() and not line.strip().startswith('--')]

setuptools.setup(
    ...
    tests_require=tests_require,
    ...
)
chown
  • 51,908
  • 16
  • 134
  • 170
2

If you use the following command, Tox will install your test_requires before running the tests:

commands = {envpython} setup.py test

You'll also need to add to setup.py where are the tests with this:

test_suite="tests_module"

Finally, here's an answer for a similar question with a nice example.

Javier
  • 2,752
  • 15
  • 30