Is it possible to change the capturing behavior in pytest for just one test — i.e., within the test script?
I have a bunch of tests that I use with pytest. There are several useful quantities that I like to print during some tests, so I use the -s flag to show them in the pytest output. But I also test for warnings, which also get printed, and look ugly and distracting. I've tried using the warnings.simplefilter as usual to just not show the warnings, but that doesn't seem to do anything. (Maybe pytest hacks it???) Anyway, I'd like some way to quiet the warnings but still check that they are raised, while also being able to see the captured output from my other print statements. Is there any way to do this — e.g., by change the capture for just one test function?
With pytest 3.x there is an easy way to temporarily disable capturing (see the section about capsys.disabled().
There's also the pytest-warnings plugin which shows the warning in a dedicated report section.
I've done it by manually redirecting stderr:
import os
import sys
import warnings
import pytest
def test():
stderr = sys.stderr
sys.stderr = open(os.devnull, 'w')
with pytest.warns(UserWarning):
warnings.warn("Warning!", UserWarning)
sys.stderr = stderr
For good measure, I could similarly redirect stdout to devnull, if other print statements are not wanted.
Related
I generally like the pytest warnings capture hook, as I can use it to force my test suite to not have any warnings triggered. However, I have one test that requires the warnings to print to stderr correctly to work.
How can I disable the warnings capture for just the one test?
For instance, something like
def test_warning():
mystderr = StringIO()
sys.stderr = mystderr
warnings.warn('warning')
assert 'UserWarning: warning' in mystderr.getvalue()
(I know I can use capsys, I just want to show the basic idea)
Thanks to the narrowing down in this discussion, I think the question might better be titled "In pytest, how to capture warnings and their standard error output in a single test?". Given that suggested rewording, I think the answer is "it can't, you need a separate test".
If there were no standard error capture requirement, you should be able to use the #pytest.mark.filterwarnings annotation for this.
#pytest.mark.filterwarnings("ignore")
def test_one():
assert api_v1() == 1
From:
https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings
#wim points out in a comment this will not capture the warning, though, and the answer he lays out captures and asserts on the warnings in a standard way.
If there were stderr output but not Python warnings thrown, capsys would be the technique, as you say
https://docs.pytest.org/en/latest/capture.html
I don't think it's meaningful to do both in a pytest test, because of the nature of the pytest implementation.
As previously noted pytest redirects stderr etc to an internal recorder. Secondly, it defines its own warnings handler
https://github.com/pytest-dev/pytest/blob/master/src/_pytest/warnings.py#L59
It is similar in idea to the answer to this question:
https://stackoverflow.com/a/5645133/5729872
I had a little poke around with redefining warnings.showwarning(), which worked fine from vanilla python, but pytest deliberately reinitializes that as well.
won't work in pytest, only straight python -->
def func(x):
warnings.warn('wwarn')
print(warnings.showwarning.__doc__)
# print('ewarn', file=sys.stderr)
return x + 1
sworig = warnings.showwarning
def showwarning_wrapper(message, category, filename, lineno, file=None, line=None):
"""Local override for showwarning()"""
print('swwrapper({})'.format(file) )
sworig(message,category,filename,lineno,file,line)
warnings.showwarning = showwarning_wrapper
<-- won't work in pytest, only straight python
You could probably put a warnings handler in your test case that reoutput to stderr ... but that doesn't prove much about the code under test, at that point.
It is your system at the end of the day. If after consideration of the point made by #wim that testing stderr as such may not prove much, you decide you still need it, I suggest separating the testing of the Python warning object (python caller layer) and the contents of stderr (calling shell layer). The first test would look at Python warning objects only. The new second test case would call the library under test as a script, through popen() or similar, and assert on the resulting standard error and output.
I'll encourage you to think about this problem in a different way.
When you want to assert that some of your code triggers warnings, you should be using a pytest.warns context for that. Check the warning message by using the match keyword, and avoid the extra complications of trying to capture it from stderr.
import re
import warnings
import pytest
def test_warning():
expected_warning_message = "my warning"
match = re.escape(expected_warning_message)
with pytest.warns(UserWarning, match=match):
warnings.warn("my warning", UserWarning)
That should be the edge of your testing responsibility. It is not your responsibility to test that the warnings module itself prints some output to stderr, because that behavior is coming from standard library code and it should be tested by Python itself.
I have a hundred or so unit tests I'm running with nose. When I change something in my models obviously I get fails, with some errors mixed in. Is there an easy way to tell nose to only log the errors? Then I don't have to go through pages of fails to look for one error log.
nose provides tools for testing exceptions (like unittest does). Try this example (and read about the other tools at Nose Testing Tools
from nose.tools import *
l = []
d = dict()
#raises(Exception)
def test_Exception1():
'''this test should pass'''
l.pop()
#raises(KeyError)
def test_Exception2():
'''this test should pass'''
d[1]
An alternative, is to redirect output to stdout and use grep (adjust the number of lines, 15 in this example, to your liking):
nosetests tests.py 2>&1 | grep "ERROR" -A 15
Another alternative is to use --pdb-errors to stop on every error and open the debugger.
It's not what you asked, but it's what I ended up using.
Sometimes I want to just insert some print statements in my code, and see what gets printed out when I exercise it. My usual way to "exercise" it is with existing pytest tests. But when I run these, I don't seem able to see any standard output (at least from within PyCharm, my IDE).
Is there a simple way to see standard output during a pytest run?
The -s switch disables per-test capturing (only if a test fails).
-s is equivalent to --capture=no.
pytest captures the stdout from individual tests and displays them only on certain conditions, along with the summary of the tests it prints by default.
Extra summary info can be shown using the '-r' option:
pytest -rP
shows the captured output of passed tests.
pytest -rx
shows the captured output of failed tests (default behaviour).
The formatting of the output is prettier with -r than with -s.
When running the test use the -s option. All print statements in exampletest.py would get printed on the console when test is run.
py.test exampletest.py -s
In an upvoted comment to the accepted answer, Joe asks:
Is there any way to print to the console AND capture the output so that it shows in the junit report?
In UNIX, this is commonly referred to as teeing. Ideally, teeing rather than capturing would be the py.test default. Non-ideally, neither py.test nor any existing third-party py.test plugin (...that I know of, anyway) supports teeing – despite Python trivially supporting teeing out-of-the-box.
Monkey-patching py.test to do anything unsupported is non-trivial. Why? Because:
Most py.test functionality is locked behind a private _pytest package not intended to be externally imported. Attempting to do so without knowing what you're doing typically results in the public pytest package raising obscure exceptions at runtime. Thanks alot, py.test. Really robust architecture you got there.
Even when you do figure out how to monkey-patch the private _pytest API in a safe manner, you have to do so before running the public pytest package run by the external py.test command. You cannot do this in a plugin (e.g., a top-level conftest module in your test suite). By the time py.test lazily gets around to dynamically importing your plugin, any py.test class you wanted to monkey-patch has long since been instantiated – and you do not have access to that instance. This implies that, if you want your monkey-patch to be meaningfully applied, you can no longer safely run the external py.test command. Instead, you have to wrap the running of that command with a custom setuptools test command that (in order):
Monkey-patches the private _pytest API.
Calls the public pytest.main() function to run the py.test command.
This answer monkey-patches py.test's -s and --capture=no options to capture stderr but not stdout. By default, these options capture neither stderr nor stdout. This isn't quite teeing, of course. But every great journey begins with a tedious prequel everyone forgets in five years.
Why do this? I shall now tell you. My py.test-driven test suite contains slow functional tests. Displaying the stdout of these tests is helpful and reassuring, preventing leycec from reaching for killall -9 py.test when yet another long-running functional test fails to do anything for weeks on end. Displaying the stderr of these tests, however, prevents py.test from reporting exception tracebacks on test failures. Which is completely unhelpful. Hence, we coerce py.test to capture stderr but not stdout.
Before we get to it, this answer assumes you already have a custom setuptools test command invoking py.test. If you don't, see the Manual Integration subsection of py.test's well-written Good Practices page.
Do not install pytest-runner, a third-party setuptools plugin providing a custom setuptools test command also invoking py.test. If pytest-runner is already installed, you'll probably need to uninstall that pip3 package and then adopt the manual approach linked to above.
Assuming you followed the instructions in Manual Integration highlighted above, your codebase should now contain a PyTest.run_tests() method. Modify this method to resemble:
class PyTest(TestCommand):
.
.
.
def run_tests(self):
# Import the public "pytest" package *BEFORE* the private "_pytest"
# package. While importation order is typically ignorable, imports can
# technically have side effects. Tragicomically, that is the case here.
# Importing the public "pytest" package establishes runtime
# configuration required by submodules of the private "_pytest" package.
# The former *MUST* always be imported before the latter. Failing to do
# so raises obtuse exceptions at runtime... which is bad.
import pytest
from _pytest.capture import CaptureManager, FDCapture, MultiCapture
# If the private method to be monkey-patched no longer exists, py.test
# is either broken or unsupported. In either case, raise an exception.
if not hasattr(CaptureManager, '_getcapture'):
from distutils.errors import DistutilsClassError
raise DistutilsClassError(
'Class "pytest.capture.CaptureManager" method _getcapture() '
'not found. The current version of py.test is either '
'broken (unlikely) or unsupported (likely).'
)
# Old method to be monkey-patched.
_getcapture_old = CaptureManager._getcapture
# New method applying this monkey-patch. Note the use of:
#
# * "out=False", *NOT* capturing stdout.
# * "err=True", capturing stderr.
def _getcapture_new(self, method):
if method == "no":
return MultiCapture(
out=False, err=True, in_=False, Capture=FDCapture)
else:
return _getcapture_old(self, method)
# Replace the old with the new method.
CaptureManager._getcapture = _getcapture_new
# Run py.test with all passed arguments.
errno = pytest.main(self.pytest_args)
sys.exit(errno)
To enable this monkey-patch, run py.test as follows:
python setup.py test -a "-s"
Stderr but not stdout will now be captured. Nifty!
Extending the above monkey-patch to tee stdout and stderr is left as an exercise to the reader with a barrel-full of free time.
According to pytest documentation, version 3 of pytest can temporary disable capture in a test:
def test_disabling_capturing(capsys):
print('this output is captured')
with capsys.disabled():
print('output not captured, going directly to sys.stdout')
print('this output is also captured')
pytest --capture=tee-sys was recently added (v5.4.0). You can capture as well as see the output on stdout/err.
Try pytest -s -v test_login.py for more info in console.
-v it's a short --verbose
-s means 'disable all capturing'
You can also enable live-logging by setting the following in pytest.ini or tox.ini in your project root.
[pytest]
log_cli = True
Or specify it directly on cli
pytest -o log_cli=True
pytest test_name.py -v -s
Simple!
I would suggest using -h command. There're quite interesting commands might be used for.
but, for this particular case: -s shortcut for --capture=no. is enough
pytest <test_file.py> -s
If you are using logging, you need to specify to turn on logging output in addition to -s for generic stdout. Based on Logging within pytest tests, I am using:
pytest --log-cli-level=DEBUG -s my_directory/
If you are using PyCharm IDE, then you can run that individual test or all tests using Run toolbar. The Run tool window displays output generated by your application and you can see all the print statements in there as part of test output.
If anyone wants to run tests from code with output:
if __name__ == '__main__':
pytest.main(['--capture=no'])
The capsys, capsysbinary, capfd, and capfdbinary fixtures allow access to stdout/stderr output created
during test execution. Here is an example test function that performs some output related checks:
def test_print_something_even_if_the_test_pass(self, capsys):
text_to_be_printed = "Print me when the test pass."
print(text_to_be_printed)
p_t = capsys.readouterr()
sys.stdout.write(p_t.out)
# the two rows above will print the text even if the test pass.
Here is the result:
test_print_something_even_if_the_test_pass PASSED [100%]Print me when the test pass.
Im using unittest and it prints ".", "E" or "F" for "ok", "error" and "fail" after each test it does. How do I switch it off ? Im using Python 2.7 and these print come from the runner class which is built in.
It sounds very tough to override the classes because it's all nested.
edit:
I only want to take off the characters E . and F because they don't appear at the same time as some other log in my tests.
The output of unittest is written to the standard error stream, which you can pipe somewhere else. On a *nix box this would be possible like this:
python -m unittest some_module 2> /dev/null
On windows, this should look like this (thanks Karl Knechtel):
python -m unittest some_module 2> NUL
If you run the tests from python, you can simply replace the stderr stream like that:
import sys, os
sys.stderr = open(os.devnull, 'w')
... # do your testing here
sys.stderr = sys.__stderr__ # if you still need the stderr stream
Since you just want to turn off the updates for the ., F, E symbols, you could also create your own TestResult class by overriding the default one. In my case (Python 2.6) this would look like this:
import unittest
class MyTestResult(unittest._TextTestResult):
def addSuccess(self, test):
TestResult.addSuccess(self, test)
def addError(self, test, err):
TestResult.addError(self, test, err)
def addFailure(self, test, err):
TestResult.addFailure(self, test, err)
This effectively turns off the printing of the characters, but maintaining the default functionality.
Now we also need a new TestRunner class and override the _makeResult method:
class MyTestRunner(unittest.TextTestRunner):
def _makeResult(self):
return MyTestResult(self.stream, self.descriptions, self.verbosity)
With this runner you can now enjoy a log free testing.
Just a note: this is not possible from the command line, unfortunately.
A bit late response, but someone may find it useful.
You can turn . E and F off by setting verbosity level to 0:
testRunner = unittest.TextTestRunner( verbosity = 0 )
You will still have the final result and possible errors/exceptions at the end of tests in the stderr.
Tested in Python 2.4 and 2.7.
Depending the unittest framework you're using (standard, nose...), you have multiple way to decrease the verbosity:
python -m unittest -h
...
-q, --quiet Minimal output
...
Whenever I run a script importing packages with import in RPy2 in Python, there are always some extra lines popping up in the console. I pasted in an example below. How can I suppress that behavior?
CookieJar:r cookies$ python script.py
‘tseries’ version: 0.10-24
‘tseries’ is a package for time series analysis and computational
finance.
See ‘library(help="tseries")’ for details.
Besides require(tseries, quietly = TRUE) and using sink(), or its Python equivalent, there is also the simple
suppressMessages( library( tseries ))
which I prefer.
You could temporarily redirect the output stream to a blackhole just before the spammy peice of code.
import sys
class Blackhole(object):
def write(self, string):
pass
stdout = sys.stdout
sys.stdout = Blackhole()
function_el_spammo()
sys.stdout = stdout
In your R script, I would preload the tseries package (just in case if its called by some other functio/package) using
require(tseries, quietly = TRUE)