Merge remote-tracking branch 'upstream/master' into merge-master-into-features
# Conflicts: # _pytest/capture.py # _pytest/compat.py # _pytest/python.py # testing/python/collect.py # testing/test_mark.py
This commit is contained in:
commit
f3b359f5b8
|
@ -31,7 +31,7 @@ env:
|
|||
matrix:
|
||||
include:
|
||||
- env: TOXENV=py36
|
||||
python: '3.6-dev'
|
||||
python: '3.6'
|
||||
- env: TOXENV=py37
|
||||
python: 'nightly'
|
||||
allow_failures:
|
||||
|
|
5
AUTHORS
5
AUTHORS
|
@ -6,6 +6,7 @@ Contributors include::
|
|||
Abdeali JK
|
||||
Abhijeet Kasurde
|
||||
Ahn Ki-Wook
|
||||
Alexander Johnson
|
||||
Alexei Kozlenok
|
||||
Anatoly Bubenkoff
|
||||
Andreas Zeidler
|
||||
|
@ -86,6 +87,7 @@ Justyna Janczyszyn
|
|||
Kale Kundert
|
||||
Katarzyna Jachim
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Lee Kamentsky
|
||||
Lev Maximov
|
||||
Loic Esteve
|
||||
|
@ -122,6 +124,7 @@ Oliver Bestwalter
|
|||
Omar Kohl
|
||||
Omer Hadari
|
||||
Patrick Hayes
|
||||
Paweł Adamczak
|
||||
Pieter Mulder
|
||||
Piotr Banaszkiewicz
|
||||
Punyashloka Biswal
|
||||
|
@ -140,6 +143,7 @@ Russel Winder
|
|||
Ryan Wooden
|
||||
Samuele Pedroni
|
||||
Simon Gomizelj
|
||||
Skylar Downes
|
||||
Stefan Farmbauer
|
||||
Stefan Zimmermann
|
||||
Stefano Taschini
|
||||
|
@ -155,5 +159,6 @@ Vasily Kuznetsov
|
|||
Victor Uriarte
|
||||
Vlad Dragos
|
||||
Vidar T. Fauske
|
||||
Vitaly Lashmanov
|
||||
Wouter van Ackooy
|
||||
Xuecong Liao
|
||||
|
|
|
@ -121,7 +121,22 @@ Bug Fixes
|
|||
3.0.8 (unreleased)
|
||||
==================
|
||||
|
||||
*
|
||||
* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
|
||||
than ValueErrors in the ``fileno`` method (`#2276`_).
|
||||
Thanks `@metasyn`_ for the PR.
|
||||
|
||||
* Fix exception formatting while importing modules when the exception message
|
||||
contains non-ascii characters (`#2336`_).
|
||||
Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR.
|
||||
|
||||
* Added documentation related to issue (`#1937`_)
|
||||
Thanks `@skylarjhdownes`_ for the PR.
|
||||
|
||||
* Allow collecting files with any file extension as Python modules (`#2369`_).
|
||||
Thanks `@Kodiologist`_ for the PR.
|
||||
|
||||
* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_).
|
||||
Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR.
|
||||
|
||||
*
|
||||
|
||||
|
@ -129,7 +144,20 @@ Bug Fixes
|
|||
|
||||
*
|
||||
|
||||
*
|
||||
|
||||
|
||||
.. _@skylarjhdownes: https://github.com/skylarjhdownes
|
||||
.. _@fabioz: https://github.com/fabioz
|
||||
.. _@metasyn: https://github.com/metasyn
|
||||
.. _@Kodiologist: https://github.com/Kodiologist
|
||||
.. _@robin0371: https://github.com/robin0371
|
||||
|
||||
|
||||
.. _#1937: https://github.com/pytest-dev/pytest/issues/1937
|
||||
.. _#2276: https://github.com/pytest-dev/pytest/issues/2276
|
||||
.. _#2336: https://github.com/pytest-dev/pytest/issues/2336
|
||||
.. _#2369: https://github.com/pytest-dev/pytest/issues/2369
|
||||
.. _#2383: https://github.com/pytest-dev/pytest/issues/2383
|
||||
|
||||
|
||||
3.0.7 (2017-03-14)
|
||||
|
@ -138,7 +166,7 @@ Bug Fixes
|
|||
|
||||
* Fix issue in assertion rewriting breaking due to modules silently discarding
|
||||
other modules when importing fails
|
||||
Notably, importing the `anydbm` module is fixed. (`#2248`_).
|
||||
Notably, importing the ``anydbm`` module is fixed. (`#2248`_).
|
||||
Thanks `@pfhayes`_ for the PR.
|
||||
|
||||
* junitxml: Fix problematic case where system-out tag occured twice per testcase
|
||||
|
@ -401,6 +429,7 @@ Bug Fixes
|
|||
|
||||
|
||||
|
||||
|
||||
3.0.2 (2016-09-01)
|
||||
==================
|
||||
|
||||
|
|
15
README.rst
15
README.rst
|
@ -6,13 +6,20 @@
|
|||
------
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
|
||||
.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg
|
||||
:target: https://anaconda.org/conda-forge/pytest
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
:target: https://pypi.python.org/pypi/pytest
|
||||
|
||||
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||
|
||||
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
|
||||
:target: https://travis-ci.org/pytest-dev/pytest
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||
|
||||
|
@ -34,7 +41,7 @@ An example of a simple test:
|
|||
To execute it::
|
||||
|
||||
$ pytest
|
||||
============================= test session starts =============================
|
||||
============================= test session starts =============================
|
||||
collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
|
|
|
@ -3,13 +3,14 @@ import sys
|
|||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
import re
|
||||
from weakref import ref
|
||||
from _pytest.compat import _PY2, _PY3, PY35
|
||||
|
||||
import py
|
||||
builtin_repr = repr
|
||||
|
||||
reprlib = py.builtin._tryimport('repr', 'reprlib')
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
if _PY3:
|
||||
from traceback import format_exception_only
|
||||
else:
|
||||
from ._py2traceback import format_exception_only
|
||||
|
@ -353,7 +354,7 @@ class ExceptionInfo(object):
|
|||
help for navigating the traceback.
|
||||
"""
|
||||
_striptext = ''
|
||||
_assert_start_repr = "AssertionError(u\'assert " if sys.version_info[0] < 3 else "AssertionError(\'assert "
|
||||
_assert_start_repr = "AssertionError(u\'assert " if _PY2 else "AssertionError(\'assert "
|
||||
|
||||
def __init__(self, tup=None, exprinfo=None):
|
||||
import _pytest._code
|
||||
|
@ -618,7 +619,7 @@ class FormattedExcinfo(object):
|
|||
|
||||
|
||||
def repr_excinfo(self, excinfo):
|
||||
if sys.version_info[0] < 3:
|
||||
if _PY2:
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
|
||||
|
@ -655,7 +656,7 @@ class FormattedExcinfo(object):
|
|||
class TerminalRepr(object):
|
||||
def __str__(self):
|
||||
s = self.__unicode__()
|
||||
if sys.version_info[0] < 3:
|
||||
if _PY2:
|
||||
s = s.encode('utf-8')
|
||||
return s
|
||||
|
||||
|
@ -851,7 +852,7 @@ def getrawcode(obj, trycall=True):
|
|||
return obj
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
|
||||
if PY35: # RecursionError introduced in 3.5
|
||||
def is_recursion_error(excinfo):
|
||||
return excinfo.errisinstance(RecursionError) # noqa
|
||||
else:
|
||||
|
|
|
@ -28,6 +28,7 @@ _PY2 = not _PY3
|
|||
NoneType = type(None)
|
||||
NOTSET = object()
|
||||
|
||||
PY35 = sys.version_info[:2] >= (3, 5)
|
||||
PY36 = sys.version_info[:2] >= (3, 6)
|
||||
MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError'
|
||||
|
||||
|
@ -250,8 +251,10 @@ else:
|
|||
try:
|
||||
return str(v)
|
||||
except UnicodeError:
|
||||
if not isinstance(v, unicode):
|
||||
v = unicode(v)
|
||||
errors = 'replace'
|
||||
return v.encode('ascii', errors)
|
||||
return v.encode('utf-8', errors)
|
||||
|
||||
|
||||
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||
|
|
|
@ -826,8 +826,8 @@ class FixtureFunctionMarker(object):
|
|||
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
||||
""" (return a) decorator to mark a fixture factory function.
|
||||
|
||||
This decorator can be used (with or or without parameters) to define
|
||||
a fixture function. The name of the fixture function can later be
|
||||
This decorator can be used (with or without parameters) to define a
|
||||
fixture function. The name of the fixture function can later be
|
||||
referenced to cause its invocation ahead of running tests: test
|
||||
modules or classes can use the pytest.mark.usefixtures(fixturename)
|
||||
marker. Test functions can directly use fixture names as input
|
||||
|
@ -846,16 +846,16 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
|
|||
reference is needed to activate the fixture.
|
||||
|
||||
:arg ids: list of string ids each corresponding to the params
|
||||
so that they are part of the test id. If no ids are provided
|
||||
they will be generated automatically from the params.
|
||||
so that they are part of the test id. If no ids are provided
|
||||
they will be generated automatically from the params.
|
||||
|
||||
:arg name: the name of the fixture. This defaults to the name of the
|
||||
decorated function. If a fixture is used in the same module in
|
||||
which it is defined, the function name of the fixture will be
|
||||
shadowed by the function arg that requests the fixture; one way
|
||||
to resolve this is to name the decorated function
|
||||
``fixture_<fixturename>`` and then use
|
||||
``@pytest.fixture(name='<fixturename>')``.
|
||||
decorated function. If a fixture is used in the same module in
|
||||
which it is defined, the function name of the fixture will be
|
||||
shadowed by the function arg that requests the fixture; one way
|
||||
to resolve this is to name the decorated function
|
||||
``fixture_<fixturename>`` and then use
|
||||
``@pytest.fixture(name='<fixturename>')``.
|
||||
|
||||
Fixtures can optionally provide their values to test functions using a ``yield`` statement,
|
||||
instead of ``return``. In this case, the code block after the ``yield`` statement is executed
|
||||
|
|
|
@ -452,9 +452,10 @@ class Testdir(object):
|
|||
the module is re-imported.
|
||||
"""
|
||||
for name in set(sys.modules).difference(self._savemodulekeys):
|
||||
# zope.interface (used by twisted-related tests) keeps internal
|
||||
# state and can't be deleted
|
||||
if not name.startswith("zope.interface"):
|
||||
# some zope modules used by twisted-related tests keeps internal
|
||||
# state and can't be deleted; we had some trouble in the past
|
||||
# with zope.interface for example
|
||||
if not name.startswith("zope"):
|
||||
del sys.modules[name]
|
||||
|
||||
def make_hook_recorder(self, pluginmanager):
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function
|
|||
import fnmatch
|
||||
import inspect
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
import math
|
||||
from itertools import count
|
||||
|
@ -20,7 +21,7 @@ from _pytest.compat import (
|
|||
isclass, isfunction, is_generator, _escape_strings,
|
||||
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
|
||||
get_real_func, getfslineno, safe_getattr,
|
||||
getlocation, enum,
|
||||
safe_str, getlocation, enum,
|
||||
)
|
||||
from _pytest.runner import fail
|
||||
|
||||
|
@ -223,8 +224,7 @@ class PyobjMixin(PyobjContext):
|
|||
continue
|
||||
name = node.name
|
||||
if isinstance(node, Module):
|
||||
assert name.endswith(".py")
|
||||
name = name[:-3]
|
||||
name = os.path.splitext(name)[0]
|
||||
if stopatmodule:
|
||||
if includemodule:
|
||||
parts.append(name)
|
||||
|
@ -427,7 +427,7 @@ class Module(main.File, PyCollector):
|
|||
if self.config.getoption('verbose') < 2:
|
||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||
exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
|
||||
formatted_tb = py._builtin._totext(exc_repr)
|
||||
formatted_tb = safe_str(exc_repr)
|
||||
raise self.CollectError(
|
||||
"ImportError while importing test module '{fspath}'.\n"
|
||||
"Hint: make sure your test modules/packages have valid Python names.\n"
|
||||
|
@ -843,7 +843,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
|||
for callspec in self._calls or [CallSpec2(self)]:
|
||||
elements = zip(ids, parameters, count())
|
||||
for a_id, param, param_index in elements:
|
||||
assert len(param.values) == len(argnames)
|
||||
if len(param.values) != len(argnames):
|
||||
raise ValueError(
|
||||
'In "parametrize" the number of values ({0}) must be '
|
||||
'equal to the number of names ({1})'.format(
|
||||
param.values, argnames))
|
||||
newcallspec = callspec.copy(self)
|
||||
newcallspec.setmulti(valtypes, argnames, param.values, a_id,
|
||||
param.deprecated_arg_dict, scopenum, param_index)
|
||||
|
|
|
@ -270,12 +270,21 @@ supporting modules which are not themselves test modules will not be rewritten.
|
|||
|
||||
.. note::
|
||||
|
||||
``pytest`` rewrites test modules on import. It does this by using an import
|
||||
hook to write new pyc files. Most of the time this works transparently.
|
||||
``pytest`` rewrites test modules on import by using an import
|
||||
hook to write new ``pyc`` files. Most of the time this works transparently.
|
||||
However, if you are messing with import yourself, the import hook may
|
||||
interfere. If this is the case, use ``--assert=plain``. Additionally,
|
||||
rewriting will fail silently if it cannot write new pycs, i.e. in a read-only
|
||||
filesystem or a zipfile.
|
||||
interfere.
|
||||
|
||||
If this is the case you have two options:
|
||||
|
||||
* Disable rewriting for a specific module by adding the string
|
||||
``PYTEST_DONT_REWRITE`` to its docstring.
|
||||
|
||||
* Disable rewriting for all modules by using ``--assert=plain``.
|
||||
|
||||
Additionally, rewriting will fail silently if it cannot write new ``.pyc`` files,
|
||||
i.e. in a read-only filesystem or a zipfile.
|
||||
|
||||
|
||||
For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ Full pytest documentation
|
|||
|
||||
getting-started
|
||||
usage
|
||||
existingtestsuite
|
||||
assert
|
||||
builtin
|
||||
fixture
|
||||
|
|
|
@ -45,11 +45,11 @@ Here is the algorithm which finds the rootdir from ``args``:
|
|||
matched, it becomes the ini-file and its directory becomes the rootdir.
|
||||
|
||||
- if no ini-file was found, use the already determined common ancestor as root
|
||||
directory. This allows to work with pytest in structures that are not part of
|
||||
directory. This allows the use of pytest in structures that are not part of
|
||||
a package and don't have any particular ini-file configuration.
|
||||
|
||||
If no ``args`` are given, pytest collects test below the current working
|
||||
directory and also starts determining the rootdir from there.
|
||||
directory and also starts determining the rootdir from there.
|
||||
|
||||
:warning: custom pytest plugin commandline arguments may include a path, as in
|
||||
``pytest --log-output ../../test.log args``. Then ``args`` is mandatory,
|
||||
|
@ -97,6 +97,8 @@ check for ini-files as follows::
|
|||
.. _`how to change command line options defaults`:
|
||||
.. _`adding default options`:
|
||||
|
||||
|
||||
|
||||
How to change command line options defaults
|
||||
------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
.. _existingtestsuite:
|
||||
|
||||
Using pytest with an existing test suite
|
||||
===========================================
|
||||
|
||||
Pytest can be used with most existing test suites, but its
|
||||
behavior differs from other test runners such as :ref:`nose <noseintegration>` or
|
||||
Python's default unittest framework.
|
||||
|
||||
Before using this section you will want to :ref:`install pytest <getstarted>`.
|
||||
|
||||
Running an existing test suite with pytest
|
||||
---------------------------------------------
|
||||
|
||||
Say you want to contribute to an existing repository somewhere.
|
||||
After pulling the code into your development space using some
|
||||
flavor of version control and (optionally) setting up a virtualenv
|
||||
you will want to run::
|
||||
|
||||
cd <repository>
|
||||
pip install -e . # Environment dependent alternatives include
|
||||
# 'python setup.py develop' and 'conda develop'
|
||||
|
||||
in your project root. This will set up a symlink to your code in
|
||||
site-packages, allowing you to edit your code while your tests
|
||||
run against it as if it were installed.
|
||||
|
||||
Setting up your project in development mode lets you avoid having to
|
||||
reinstall every time you want to run your tests, and is less brittle than
|
||||
mucking about with sys.path to point your tests at local code.
|
||||
|
||||
Also consider using :ref:`tox <use tox>`.
|
||||
|
||||
.. include:: links.inc
|
|
@ -253,7 +253,7 @@ the code after the *yield* statement serves as the teardown code:
|
|||
import pytest
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
def smtp():
|
||||
smtp = smtplib.SMTP("smtp.gmail.com")
|
||||
yield smtp # provide the fixture value
|
||||
print("teardown smtp")
|
||||
|
@ -287,7 +287,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen
|
|||
import pytest
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
def smtp():
|
||||
with smtplib.SMTP("smtp.gmail.com") as smtp:
|
||||
yield smtp # provide the fixture value
|
||||
|
||||
|
|
|
@ -49,17 +49,17 @@ That's it. You can execute the test function now::
|
|||
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
|
||||
|
||||
test_sample.py F
|
||||
|
||||
|
||||
======= FAILURES ========
|
||||
_______ test_answer ________
|
||||
|
||||
|
||||
def test_answer():
|
||||
> assert func(3) == 5
|
||||
E assert 4 == 5
|
||||
E + where 4 = func(3)
|
||||
|
||||
|
||||
test_sample.py:5: AssertionError
|
||||
======= 1 failed in 0.12 seconds ========
|
||||
|
||||
|
@ -128,15 +128,15 @@ run the module by passing its filename::
|
|||
.F
|
||||
======= FAILURES ========
|
||||
_______ TestClass.test_two ________
|
||||
|
||||
|
||||
self = <test_class.TestClass object at 0xdeadbeef>
|
||||
|
||||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
> assert hasattr(x, 'check')
|
||||
E AssertionError: assert False
|
||||
E + where False = hasattr('hello', 'check')
|
||||
|
||||
|
||||
test_class.py:8: AssertionError
|
||||
1 failed, 1 passed in 0.12 seconds
|
||||
|
||||
|
@ -165,14 +165,14 @@ before performing the test function call. Let's just run it::
|
|||
F
|
||||
======= FAILURES ========
|
||||
_______ test_needsfiles ________
|
||||
|
||||
|
||||
tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
|
||||
|
||||
|
||||
def test_needsfiles(tmpdir):
|
||||
print (tmpdir)
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
|
||||
test_tmpdir.py:3: AssertionError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
PYTEST_TMPDIR/test_needsfiles0
|
||||
|
@ -192,6 +192,7 @@ Here are a few suggestions where to go next:
|
|||
|
||||
* :ref:`cmdline` for command line invocation examples
|
||||
* :ref:`good practices <goodpractices>` for virtualenv, test layout
|
||||
* :ref:`existingtestsuite` for working with pre-existing tests
|
||||
* :ref:`fixtures` for providing a functional baseline to your tests
|
||||
* :ref:`plugins` managing and writing plugins
|
||||
|
||||
|
|
|
@ -30,68 +30,106 @@ Within Python modules, ``pytest`` also discovers tests using the standard
|
|||
|
||||
|
||||
Choosing a test layout / import rules
|
||||
------------------------------------------
|
||||
-------------------------------------
|
||||
|
||||
``pytest`` supports two common test layouts:
|
||||
|
||||
* putting tests into an extra directory outside your actual application
|
||||
code, useful if you have many functional tests or for other reasons
|
||||
want to keep tests separate from actual application code (often a good
|
||||
idea)::
|
||||
Tests outside application code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
setup.py # your setuptools Python package metadata
|
||||
Putting tests into an extra directory outside your actual application code
|
||||
might be useful if you have many functional tests or for other reasons want
|
||||
to keep tests separate from actual application code (often a good idea)::
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
__init__.py
|
||||
appmodule.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
test_app.py
|
||||
test_view.py
|
||||
...
|
||||
|
||||
This way your tests can run easily against an installed version
|
||||
of ``mypkg``.
|
||||
|
||||
* inlining test directories into your application package, useful if you
|
||||
have direct relation between (unit-)test and application modules and
|
||||
want to distribute your tests along with your application::
|
||||
Note that using this scheme your test files must have **unique names**, because
|
||||
``pytest`` will import them as *top-level* modules since there are no packages
|
||||
to derive a full package name from. In other words, the test files in the example above will
|
||||
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
|
||||
``sys.path``.
|
||||
|
||||
setup.py # your setuptools Python package metadata
|
||||
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
|
||||
``tests`` folder and subfolders, changing them to packages::
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
...
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
|
||||
you to have modules with the same name. But now this introduces a subtle problem: in order to load
|
||||
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
|
||||
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
|
||||
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
||||
because you want to test the *installed* version of your package, not the local code from the repository.
|
||||
|
||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
||||
sub-directory of your root::
|
||||
|
||||
setup.py
|
||||
src/
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
|
||||
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
||||
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
||||
|
||||
Tests as part of application code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Inlining test directories into your application package
|
||||
is useful if you have direct relation between tests and application modules and
|
||||
want to distribute them along with your application::
|
||||
|
||||
setup.py
|
||||
mypkg/
|
||||
__init__.py
|
||||
appmodule.py
|
||||
...
|
||||
app.py
|
||||
view.py
|
||||
test/
|
||||
__init__.py
|
||||
test_app.py
|
||||
test_view.py
|
||||
...
|
||||
|
||||
Important notes relating to both schemes:
|
||||
In this scheme, it is easy to your run tests using the ``--pyargs`` option::
|
||||
|
||||
- **make sure that "mypkg" is importable**, for example by typing once::
|
||||
pytest --pyargs mypkg
|
||||
|
||||
pip install -e . # install package using setup.py in editable mode
|
||||
``pytest`` will discover where ``mypkg`` is installed and collect tests from there.
|
||||
|
||||
- **avoid "__init__.py" files in your test directories**.
|
||||
This way your tests can run easily against an installed version
|
||||
of ``mypkg``, independently from the installed package if it contains
|
||||
the tests or not.
|
||||
Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section.
|
||||
|
||||
- With inlined tests you might put ``__init__.py`` into test
|
||||
directories and make them installable as part of your application.
|
||||
Using the ``pytest --pyargs mypkg`` invocation pytest will
|
||||
discover where mypkg is installed and collect tests from there.
|
||||
With the "external" test you can still distribute tests but they
|
||||
will not be installed or become importable.
|
||||
|
||||
Typically you can run tests by pointing to test directories or modules::
|
||||
|
||||
pytest tests/test_app.py # for external test dirs
|
||||
pytest mypkg/test/test_app.py # for inlined test dirs
|
||||
pytest mypkg # run tests in all below test directories
|
||||
pytest # run all tests below current dir
|
||||
...
|
||||
|
||||
Because of the above ``editable install`` mode you can change your
|
||||
source code (both tests and the app) and rerun tests at will.
|
||||
Once you are done with your work, you can `use tox`_ to make sure
|
||||
that the package is really correct and tests pass in all
|
||||
required configurations.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -144,7 +182,15 @@ for installing your application and any dependencies
|
|||
as well as the ``pytest`` package itself. This ensures your code and
|
||||
dependencies are isolated from the system Python installation.
|
||||
|
||||
If you frequently release code and want to make sure that your actual
|
||||
You can then install your package in "editable" mode::
|
||||
|
||||
pip install -e .
|
||||
|
||||
which lets you change your source code (both tests and application) and rerun tests at will.
|
||||
This is similar to running `python setup.py develop` or `conda develop` in that it installs
|
||||
your package using a symlink to your development code.
|
||||
|
||||
Once you are done with your work and want to make sure that your actual
|
||||
package passes all tests you may want to look into `tox`_, the
|
||||
virtualenv test automation tool and its `pytest support
|
||||
<https://tox.readthedocs.io/en/latest/example/pytest.html>`_.
|
||||
|
@ -154,11 +200,6 @@ options. It will run tests against the installed package and not
|
|||
against your source code checkout, helping to detect packaging
|
||||
glitches.
|
||||
|
||||
Continuous integration services such as Jenkins_ can make use of the
|
||||
``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g.
|
||||
by publishing the results in a nice format with the `Jenkins xUnit Plugin
|
||||
<https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin>`_).
|
||||
|
||||
|
||||
Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
|
||||
--------------------------------------------------------------------------
|
||||
|
|
|
@ -47,9 +47,19 @@ Unsupported idioms / known issues
|
|||
``tests.test_mod``) but different file system paths
|
||||
(e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
|
||||
by extending sys.path/import semantics. pytest does not do that
|
||||
but there is discussion in `issue268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that
|
||||
but there is discussion in `#268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that
|
||||
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
|
||||
|
||||
If you place a conftest.py file in the root directory of your project
|
||||
(as determined by pytest) pytest will run tests "nose style" against
|
||||
the code below that directory by adding it to your ``sys.path`` instead of
|
||||
running against your installed code.
|
||||
|
||||
You may find yourself wanting to do this if you ran ``python setup.py install``
|
||||
to set up your project, as opposed to ``python setup.py develop`` or any of
|
||||
the package manager equivalents. Installing with develop in a
|
||||
virtual environment like Tox is recommended over this pattern.
|
||||
|
||||
- nose-style doctests are not collected and executed correctly,
|
||||
also doctest fixtures don't work.
|
||||
|
||||
|
@ -62,3 +72,4 @@ Unsupported idioms / known issues
|
|||
being the recommended alternative.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# pinning sphinx to 1.4.* due to search issues with rtd:
|
||||
# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25
|
||||
sphinx ==1.4.*
|
|
@ -19,6 +19,18 @@ You can invoke testing through the Python interpreter from the command line::
|
|||
This is almost equivalent to invoking the command line script ``pytest [...]``
|
||||
directly, except that python will also add the current directory to ``sys.path``.
|
||||
|
||||
Possible exit codes
|
||||
--------------------------------------------------------------
|
||||
|
||||
Running ``pytest`` can result in six different exit codes:
|
||||
|
||||
:Exit code 0: All tests were collected and passed successfully
|
||||
:Exit code 1: Tests were collected and run but some of the tests failed
|
||||
:Exit code 2: Test execution was interrupted by the user
|
||||
:Exit code 3: Internal error happened while executing tests
|
||||
:Exit code 4: pytest command line usage error
|
||||
:Exit code 5: No tests were collected
|
||||
|
||||
Getting help on version, option names, environment variables
|
||||
--------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -104,6 +104,22 @@ class TestModule(object):
|
|||
else:
|
||||
assert name not in stdout
|
||||
|
||||
def test_show_traceback_import_error_unicode(self, testdir):
|
||||
"""Check test modules collected which raise ImportError with unicode messages
|
||||
are handled properly (#2336).
|
||||
"""
|
||||
testdir.makepyfile(u"""
|
||||
# -*- coding: utf-8 -*-
|
||||
raise ImportError(u'Something bad happened ☺')
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"ImportError while importing test module*",
|
||||
"Traceback:",
|
||||
"*raise ImportError*Something bad happened*",
|
||||
])
|
||||
assert result.ret == 2
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
def test_class_with_init_warning(self, testdir):
|
||||
|
@ -826,6 +842,34 @@ class TestConftestCustomization(object):
|
|||
l = modcol.collect()
|
||||
assert '_hello' not in l
|
||||
|
||||
def test_issue2369_collect_module_fileext(self, testdir):
|
||||
"""Ensure we can collect files with weird file extensions as Python
|
||||
modules (#2369)"""
|
||||
# We'll implement a little finder and loader to import files containing
|
||||
# Python source code whose file extension is ".narf".
|
||||
testdir.makeconftest("""
|
||||
import sys, os, imp
|
||||
from _pytest.python import Module
|
||||
|
||||
class Loader:
|
||||
def load_module(self, name):
|
||||
return imp.load_source(name, name + ".narf")
|
||||
class Finder:
|
||||
def find_module(self, name, path=None):
|
||||
if os.path.exists(name + ".narf"):
|
||||
return Loader()
|
||||
sys.meta_path.append(Finder())
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.ext == ".narf":
|
||||
return Module(path, parent)""")
|
||||
testdir.makefile(".narf", """
|
||||
def test_something():
|
||||
assert 1 + 1 == 2""")
|
||||
# Use runpytest_subprocess, since we're futzing with sys.meta_path.
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines('*1 passed*')
|
||||
|
||||
def test_setup_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
|
|
|
@ -5,6 +5,7 @@ from __future__ import with_statement
|
|||
import pickle
|
||||
import os
|
||||
import sys
|
||||
from io import UnsupportedOperation
|
||||
|
||||
import _pytest._code
|
||||
import py
|
||||
|
@ -671,7 +672,7 @@ def test_dontreadfrominput():
|
|||
pytest.raises(IOError, f.read)
|
||||
pytest.raises(IOError, f.readlines)
|
||||
pytest.raises(IOError, iter, f)
|
||||
pytest.raises(ValueError, f.fileno)
|
||||
pytest.raises(UnsupportedOperation, f.fileno)
|
||||
f.close() # just for completeness
|
||||
|
||||
|
||||
|
|
|
@ -305,6 +305,23 @@ def test_parametrized_collected_from_command_line(testdir):
|
|||
rec.assertoutcome(passed=3)
|
||||
|
||||
|
||||
def test_parametrized_collect_with_wrong_args(testdir):
|
||||
"""Test collect parametrized func with wrong number of args."""
|
||||
py_file = testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize('foo, bar', [(1, 2, 3)])
|
||||
def test_func(foo, bar):
|
||||
pass
|
||||
""")
|
||||
|
||||
result = testdir.runpytest(py_file)
|
||||
result.stdout.fnmatch_lines([
|
||||
'E ValueError: In "parametrize" the number of values ((1, 2, 3)) '
|
||||
'must be equal to the number of names ([\'foo\', \'bar\'])'
|
||||
])
|
||||
|
||||
|
||||
class TestFunctional(object):
|
||||
|
||||
def test_mark_per_function(self, testdir):
|
||||
|
|
Loading…
Reference in New Issue