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:
Bruno Oliveira 2017-05-03 19:04:53 -03:00
commit f3b359f5b8
23 changed files with 327 additions and 99 deletions

View File

@ -31,7 +31,7 @@ env:
matrix: matrix:
include: include:
- env: TOXENV=py36 - env: TOXENV=py36
python: '3.6-dev' python: '3.6'
- env: TOXENV=py37 - env: TOXENV=py37
python: 'nightly' python: 'nightly'
allow_failures: allow_failures:

View File

@ -6,6 +6,7 @@ Contributors include::
Abdeali JK Abdeali JK
Abhijeet Kasurde Abhijeet Kasurde
Ahn Ki-Wook Ahn Ki-Wook
Alexander Johnson
Alexei Kozlenok Alexei Kozlenok
Anatoly Bubenkoff Anatoly Bubenkoff
Andreas Zeidler Andreas Zeidler
@ -86,6 +87,7 @@ Justyna Janczyszyn
Kale Kundert Kale Kundert
Katarzyna Jachim Katarzyna Jachim
Kevin Cox Kevin Cox
Kodi B. Arfer
Lee Kamentsky Lee Kamentsky
Lev Maximov Lev Maximov
Loic Esteve Loic Esteve
@ -122,6 +124,7 @@ Oliver Bestwalter
Omar Kohl Omar Kohl
Omer Hadari Omer Hadari
Patrick Hayes Patrick Hayes
Paweł Adamczak
Pieter Mulder Pieter Mulder
Piotr Banaszkiewicz Piotr Banaszkiewicz
Punyashloka Biswal Punyashloka Biswal
@ -140,6 +143,7 @@ Russel Winder
Ryan Wooden Ryan Wooden
Samuele Pedroni Samuele Pedroni
Simon Gomizelj Simon Gomizelj
Skylar Downes
Stefan Farmbauer Stefan Farmbauer
Stefan Zimmermann Stefan Zimmermann
Stefano Taschini Stefano Taschini
@ -155,5 +159,6 @@ Vasily Kuznetsov
Victor Uriarte Victor Uriarte
Vlad Dragos Vlad Dragos
Vidar T. Fauske Vidar T. Fauske
Vitaly Lashmanov
Wouter van Ackooy Wouter van Ackooy
Xuecong Liao Xuecong Liao

View File

@ -121,7 +121,22 @@ Bug Fixes
3.0.8 (unreleased) 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) 3.0.7 (2017-03-14)
@ -138,7 +166,7 @@ Bug Fixes
* Fix issue in assertion rewriting breaking due to modules silently discarding * Fix issue in assertion rewriting breaking due to modules silently discarding
other modules when importing fails 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. Thanks `@pfhayes`_ for the PR.
* junitxml: Fix problematic case where system-out tag occured twice per testcase * junitxml: Fix problematic case where system-out tag occured twice per testcase
@ -401,6 +429,7 @@ Bug Fixes
3.0.2 (2016-09-01) 3.0.2 (2016-09-01)
================== ==================

View File

@ -6,13 +6,20 @@
------ ------
.. image:: https://img.shields.io/pypi/v/pytest.svg .. 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 .. 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 .. 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 .. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest :target: https://travis-ci.org/pytest-dev/pytest
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true .. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest :target: https://ci.appveyor.com/project/pytestbot/pytest
@ -34,7 +41,7 @@ An example of a simple test:
To execute it:: To execute it::
$ pytest $ pytest
============================= test session starts ============================= ============================= test session starts =============================
collected 1 items collected 1 items
test_sample.py F test_sample.py F

View File

@ -3,13 +3,14 @@ import sys
from inspect import CO_VARARGS, CO_VARKEYWORDS from inspect import CO_VARARGS, CO_VARKEYWORDS
import re import re
from weakref import ref from weakref import ref
from _pytest.compat import _PY2, _PY3, PY35
import py import py
builtin_repr = repr builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib') reprlib = py.builtin._tryimport('repr', 'reprlib')
if sys.version_info[0] >= 3: if _PY3:
from traceback import format_exception_only from traceback import format_exception_only
else: else:
from ._py2traceback import format_exception_only from ._py2traceback import format_exception_only
@ -353,7 +354,7 @@ class ExceptionInfo(object):
help for navigating the traceback. help for navigating the traceback.
""" """
_striptext = '' _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): def __init__(self, tup=None, exprinfo=None):
import _pytest._code import _pytest._code
@ -618,7 +619,7 @@ class FormattedExcinfo(object):
def repr_excinfo(self, excinfo): def repr_excinfo(self, excinfo):
if sys.version_info[0] < 3: if _PY2:
reprtraceback = self.repr_traceback(excinfo) reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash() reprcrash = excinfo._getreprcrash()
@ -655,7 +656,7 @@ class FormattedExcinfo(object):
class TerminalRepr(object): class TerminalRepr(object):
def __str__(self): def __str__(self):
s = self.__unicode__() s = self.__unicode__()
if sys.version_info[0] < 3: if _PY2:
s = s.encode('utf-8') s = s.encode('utf-8')
return s return s
@ -851,7 +852,7 @@ def getrawcode(obj, trycall=True):
return obj 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): def is_recursion_error(excinfo):
return excinfo.errisinstance(RecursionError) # noqa return excinfo.errisinstance(RecursionError) # noqa
else: else:

View File

@ -28,6 +28,7 @@ _PY2 = not _PY3
NoneType = type(None) NoneType = type(None)
NOTSET = object() NOTSET = object()
PY35 = sys.version_info[:2] >= (3, 5)
PY36 = sys.version_info[:2] >= (3, 6) PY36 = sys.version_info[:2] >= (3, 6)
MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError' MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError'
@ -250,8 +251,10 @@ else:
try: try:
return str(v) return str(v)
except UnicodeError: except UnicodeError:
if not isinstance(v, unicode):
v = unicode(v)
errors = 'replace' errors = 'replace'
return v.encode('ascii', errors) return v.encode('utf-8', errors)
COLLECT_FAKEMODULE_ATTRIBUTES = ( COLLECT_FAKEMODULE_ATTRIBUTES = (

View File

@ -826,8 +826,8 @@ class FixtureFunctionMarker(object):
def fixture(scope="function", params=None, autouse=False, ids=None, name=None): def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
""" (return a) decorator to mark a fixture factory function. """ (return a) decorator to mark a fixture factory function.
This decorator can be used (with or or without parameters) to define This decorator can be used (with or without parameters) to define a
a fixture function. The name of the fixture function can later be fixture function. The name of the fixture function can later be
referenced to cause its invocation ahead of running tests: test referenced to cause its invocation ahead of running tests: test
modules or classes can use the pytest.mark.usefixtures(fixturename) modules or classes can use the pytest.mark.usefixtures(fixturename)
marker. Test functions can directly use fixture names as input 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. reference is needed to activate the fixture.
:arg ids: list of string ids each corresponding to the params :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 so that they are part of the test id. If no ids are provided
they will be generated automatically from the params. they will be generated automatically from the params.
:arg name: the name of the fixture. This defaults to the name of the :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 decorated function. If a fixture is used in the same module in
which it is defined, the function name of the fixture will be which it is defined, the function name of the fixture will be
shadowed by the function arg that requests the fixture; one way shadowed by the function arg that requests the fixture; one way
to resolve this is to name the decorated function to resolve this is to name the decorated function
``fixture_<fixturename>`` and then use ``fixture_<fixturename>`` and then use
``@pytest.fixture(name='<fixturename>')``. ``@pytest.fixture(name='<fixturename>')``.
Fixtures can optionally provide their values to test functions using a ``yield`` statement, 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 instead of ``return``. In this case, the code block after the ``yield`` statement is executed

View File

@ -452,9 +452,10 @@ class Testdir(object):
the module is re-imported. the module is re-imported.
""" """
for name in set(sys.modules).difference(self._savemodulekeys): for name in set(sys.modules).difference(self._savemodulekeys):
# zope.interface (used by twisted-related tests) keeps internal # some zope modules used by twisted-related tests keeps internal
# state and can't be deleted # state and can't be deleted; we had some trouble in the past
if not name.startswith("zope.interface"): # with zope.interface for example
if not name.startswith("zope"):
del sys.modules[name] del sys.modules[name]
def make_hook_recorder(self, pluginmanager): def make_hook_recorder(self, pluginmanager):

View File

@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function
import fnmatch import fnmatch
import inspect import inspect
import sys import sys
import os
import collections import collections
import math import math
from itertools import count from itertools import count
@ -20,7 +21,7 @@ from _pytest.compat import (
isclass, isfunction, is_generator, _escape_strings, isclass, isfunction, is_generator, _escape_strings,
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
get_real_func, getfslineno, safe_getattr, get_real_func, getfslineno, safe_getattr,
getlocation, enum, safe_str, getlocation, enum,
) )
from _pytest.runner import fail from _pytest.runner import fail
@ -223,8 +224,7 @@ class PyobjMixin(PyobjContext):
continue continue
name = node.name name = node.name
if isinstance(node, Module): if isinstance(node, Module):
assert name.endswith(".py") name = os.path.splitext(name)[0]
name = name[:-3]
if stopatmodule: if stopatmodule:
if includemodule: if includemodule:
parts.append(name) parts.append(name)
@ -427,7 +427,7 @@ class Module(main.File, PyCollector):
if self.config.getoption('verbose') < 2: if self.config.getoption('verbose') < 2:
exc_info.traceback = exc_info.traceback.filter(filter_traceback) exc_info.traceback = exc_info.traceback.filter(filter_traceback)
exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly() 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( raise self.CollectError(
"ImportError while importing test module '{fspath}'.\n" "ImportError while importing test module '{fspath}'.\n"
"Hint: make sure your test modules/packages have valid Python names.\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)]: for callspec in self._calls or [CallSpec2(self)]:
elements = zip(ids, parameters, count()) elements = zip(ids, parameters, count())
for a_id, param, param_index in elements: 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 = callspec.copy(self)
newcallspec.setmulti(valtypes, argnames, param.values, a_id, newcallspec.setmulti(valtypes, argnames, param.values, a_id,
param.deprecated_arg_dict, scopenum, param_index) param.deprecated_arg_dict, scopenum, param_index)

View File

@ -270,12 +270,21 @@ supporting modules which are not themselves test modules will not be rewritten.
.. note:: .. note::
``pytest`` rewrites test modules on import. It does this by using an import ``pytest`` rewrites test modules on import by using an import
hook to write new pyc files. Most of the time this works transparently. 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 However, if you are messing with import yourself, the import hook may
interfere. If this is the case, use ``--assert=plain``. Additionally, interfere.
rewriting will fail silently if it cannot write new pycs, i.e. in a read-only
filesystem or a zipfile. 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>`_. 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>`_.

View File

@ -12,6 +12,7 @@ Full pytest documentation
getting-started getting-started
usage usage
existingtestsuite
assert assert
builtin builtin
fixture fixture

View File

@ -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. 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 - 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. a package and don't have any particular ini-file configuration.
If no ``args`` are given, pytest collects test below the current working 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 :warning: custom pytest plugin commandline arguments may include a path, as in
``pytest --log-output ../../test.log args``. Then ``args`` is mandatory, ``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`: .. _`how to change command line options defaults`:
.. _`adding default options`: .. _`adding default options`:
How to change command line options defaults How to change command line options defaults
------------------------------------------------ ------------------------------------------------

View File

@ -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

View File

@ -253,7 +253,7 @@ the code after the *yield* statement serves as the teardown code:
import pytest import pytest
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(request): def smtp():
smtp = smtplib.SMTP("smtp.gmail.com") smtp = smtplib.SMTP("smtp.gmail.com")
yield smtp # provide the fixture value yield smtp # provide the fixture value
print("teardown smtp") print("teardown smtp")
@ -287,7 +287,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen
import pytest import pytest
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(request): def smtp():
with smtplib.SMTP("smtp.gmail.com") as smtp: with smtplib.SMTP("smtp.gmail.com") as smtp:
yield smtp # provide the fixture value yield smtp # provide the fixture value

View File

@ -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 platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
test_sample.py F test_sample.py F
======= FAILURES ======== ======= FAILURES ========
_______ test_answer ________ _______ test_answer ________
def test_answer(): def test_answer():
> assert func(3) == 5 > assert func(3) == 5
E assert 4 == 5 E assert 4 == 5
E + where 4 = func(3) E + where 4 = func(3)
test_sample.py:5: AssertionError test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ======== ======= 1 failed in 0.12 seconds ========
@ -128,15 +128,15 @@ run the module by passing its filename::
.F .F
======= FAILURES ======== ======= FAILURES ========
_______ TestClass.test_two ________ _______ TestClass.test_two ________
self = <test_class.TestClass object at 0xdeadbeef> self = <test_class.TestClass object at 0xdeadbeef>
def test_two(self): def test_two(self):
x = "hello" x = "hello"
> assert hasattr(x, 'check') > assert hasattr(x, 'check')
E AssertionError: assert False E AssertionError: assert False
E + where False = hasattr('hello', 'check') E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError test_class.py:8: AssertionError
1 failed, 1 passed in 0.12 seconds 1 failed, 1 passed in 0.12 seconds
@ -165,14 +165,14 @@ before performing the test function call. Let's just run it::
F F
======= FAILURES ======== ======= FAILURES ========
_______ test_needsfiles ________ _______ test_needsfiles ________
tmpdir = local('PYTEST_TMPDIR/test_needsfiles0') tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
def test_needsfiles(tmpdir): def test_needsfiles(tmpdir):
print (tmpdir) print (tmpdir)
> assert 0 > assert 0
E assert 0 E assert 0
test_tmpdir.py:3: AssertionError test_tmpdir.py:3: AssertionError
--------------------------- Captured stdout call --------------------------- --------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0 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:`cmdline` for command line invocation examples
* :ref:`good practices <goodpractices>` for virtualenv, test layout * :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:`fixtures` for providing a functional baseline to your tests
* :ref:`plugins` managing and writing plugins * :ref:`plugins` managing and writing plugins

View File

@ -30,68 +30,106 @@ Within Python modules, ``pytest`` also discovers tests using the standard
Choosing a test layout / import rules Choosing a test layout / import rules
------------------------------------------ -------------------------------------
``pytest`` supports two common test layouts: ``pytest`` supports two common test layouts:
* putting tests into an extra directory outside your actual application Tests outside application code
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)::
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/ mypkg/
__init__.py __init__.py
appmodule.py app.py
view.py
tests/ tests/
test_app.py 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 Note that using this scheme your test files must have **unique names**, because
have direct relation between (unit-)test and application modules and ``pytest`` will import them as *top-level* modules since there are no packages
want to distribute your tests along with your application:: 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/ mypkg/
__init__.py __init__.py
appmodule.py app.py
... view.py
test/ test/
__init__.py
test_app.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**. Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section.
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.
- 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:: .. note::
@ -144,7 +182,15 @@ for installing your application and any dependencies
as well as the ``pytest`` package itself. This ensures your code and as well as the ``pytest`` package itself. This ensures your code and
dependencies are isolated from the system Python installation. 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 package passes all tests you may want to look into `tox`_, the
virtualenv test automation tool and its `pytest support virtualenv test automation tool and its `pytest support
<https://tox.readthedocs.io/en/latest/example/pytest.html>`_. <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 against your source code checkout, helping to detect packaging
glitches. 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`` Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
-------------------------------------------------------------------------- --------------------------------------------------------------------------

View File

@ -47,9 +47,19 @@ Unsupported idioms / known issues
``tests.test_mod``) but different file system paths ``tests.test_mod``) but different file system paths
(e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``) (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
by extending sys.path/import semantics. pytest does not do that 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>`_. `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, - nose-style doctests are not collected and executed correctly,
also doctest fixtures don't work. also doctest fixtures don't work.
@ -62,3 +72,4 @@ Unsupported idioms / known issues
being the recommended alternative. being the recommended alternative.

3
doc/en/requirements.txt Normal file
View File

@ -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.*

View File

@ -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 [...]`` This is almost equivalent to invoking the command line script ``pytest [...]``
directly, except that python will also add the current directory to ``sys.path``. 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 Getting help on version, option names, environment variables
-------------------------------------------------------------- --------------------------------------------------------------

View File

@ -104,6 +104,22 @@ class TestModule(object):
else: else:
assert name not in stdout 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): class TestClass(object):
def test_class_with_init_warning(self, testdir): def test_class_with_init_warning(self, testdir):
@ -826,6 +842,34 @@ class TestConftestCustomization(object):
l = modcol.collect() l = modcol.collect()
assert '_hello' not in l 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): def test_setup_only_available_in_subdir(testdir):
sub1 = testdir.mkpydir("sub1") sub1 = testdir.mkpydir("sub1")
sub2 = testdir.mkpydir("sub2") sub2 = testdir.mkpydir("sub2")

View File

@ -5,6 +5,7 @@ from __future__ import with_statement
import pickle import pickle
import os import os
import sys import sys
from io import UnsupportedOperation
import _pytest._code import _pytest._code
import py import py
@ -671,7 +672,7 @@ def test_dontreadfrominput():
pytest.raises(IOError, f.read) pytest.raises(IOError, f.read)
pytest.raises(IOError, f.readlines) pytest.raises(IOError, f.readlines)
pytest.raises(IOError, iter, f) pytest.raises(IOError, iter, f)
pytest.raises(ValueError, f.fileno) pytest.raises(UnsupportedOperation, f.fileno)
f.close() # just for completeness f.close() # just for completeness

View File

@ -305,6 +305,23 @@ def test_parametrized_collected_from_command_line(testdir):
rec.assertoutcome(passed=3) 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): class TestFunctional(object):
def test_mark_per_function(self, testdir): def test_mark_per_function(self, testdir):

View File

@ -109,6 +109,8 @@ commands=
pytest -ra {posargs:testing/test_unittest.py} pytest -ra {posargs:testing/test_unittest.py}
[testenv:docs] [testenv:docs]
skipsdist=True
usedevelop=True
basepython=python basepython=python
changedir=doc/en changedir=doc/en
deps= deps=