I sometimes run into the following problem. I have a function, which returns something I am interested in and prints something I do not care about. E.g.
def f(x):
print('Some complicated printing stuff')
important_result = 42
return important_result
I want to write a doctest, which checks, that it indeed returns the correct result. But whose code is not obfuscated by the complicated printing stuff.
Something along the following lines would be cool:
def f(x):
"""
>>> f(0)
...
42
"""
print('Some complicated printing stuff')
important_result = 42
return important_result
Is there an elegant way to acomplish this?
Given that, your question has tag 'doctest' - I am assuming you want to run doctest for your function (enlighten me in the comment section, for any presumption or trivia) - as the text is ambiguous.
doctest basically works by looking for text in the form of Python interactive session. This text can be written in the docstring (like you have in the second code block/sample) or in a separate file.
using docstrings - all you have to do is specify few (or at-least one) examples of function i.e. passing the required parameters and the expected result, exactly in the same format as of Python interactive session. A good practice is to run your function in the interactive session and copy paste it in the docstring of the function.
To run the doctest, first specify the following code (for your mentioned code example, you just have to type the exact lines):
if __name__ = "__main__":
import doctest
doctest.testmod()
Line 1 - run the following code, only if the module (your *.py file)
is run as script i.e. not imported, etc. (a more detailed answer here)
Line 2 - importing the doctest module.
Line 3 - looks for any interactive session style text in the
docstring and runs it.
Once you have the above code included in your module (*.py file), just run in as a script:
python yourmodule.py
OR,
You can run the doctest directly (without the above 3 lines of code):
pyhton -m doctest yourmodule.py
using a separate file - add the following line in your file:
if __name__ = "__main__"
import doctest
doctest.testfile("somefile.txt")
it will identify and execute any interactive Python text in the file. By default the testfile() will look for 'somefile.txt' in the same directory where the module (.py file) is located (using options we can look for files in other locations).
Coming back to your question
I want to write a doctest, which checks, that it indeed returns the
correct result. But whose code is not obfuscated by the complicated
printing stuff. Something along the following lines would be cool:
NO (directly not possible) - Scenarios for doctest are set/written by specifying examples in form of Python interactive sessions in your docstring - exactly the i.e. as mentioned above a good practice is to run your function with various inputs in the interactive session and copy paste those lines in the docstring - and all the print statements will definitely be there, and part of the out for doctest to mark test as passed.
Indirect ways:
use an optional parameter in function, for example printing - the overhead 'd be you'll need to make changes to the function i.e. moving the print statements under if.
def f(x, printing=True):
"""
f(0, printing=False)
...
42
"""
if printing:
print('Some complicated printing stuff')
important_result = 42
return important_result
pass the None argument to print function - beware, it'll be passed to all the print calls in the module i.e. the whole *.py file.
def f(x):
"""
f(0)
...
42
"""
print('Some complicated printing stuff')
important_result = 42
return important_result
if name == 'main':
import doctest
print = lambda *args, **kwargs: None
doctest.testmod()
Source: jonrsharpe's answer
To get some feedback, I ended up printing the result of doctest.testmod()
Related
I am trying to automate testing across several modules. All of these modules have a "test()" function with their unit-tests. Some modules are basic and their tests contain simple statements, but most modules have unittest or doctest. I'm having the most trouble dynamically importing and running doctest.
For example, here is a module sample.py
class sample:
"""
>>> import sample
>>> print sample.hello()
Hello
"""
def hello():
return "Hello"
def test():
import doctest
doctest.testmod(name=__name__, verbose=True)
And here is my file run_all_tests.py:
# assume I already have a list of all my files to test
for file in all_my_files:
temp_module = __import__(file)
temp_module.test()
This doesn't work and I always get this error:
1 items had no tests:
sample
0 tests in 1 items.
0 passed and 0 failed.
Test passed.
Please help me understand the problem.
Would Nose be a good alternative? I don't want to use it because I won't know beforehand if a module uses doctests, unittests or simple statements. But do let me know if that's not true/you have another alternative entirely!
Use doctest.DocTestSuite. It takes a module, extracts all doctests that exist there, and returns it as a unittest.TestSuite. Then, running the tests is a piece of pie. Your code would look like this:
for f in all_my_files:
temp_module = __import__(f)
test_suite = doctest.DocTestSuite(temp_module)
unittest.TextTestRunner().run(test_suite)
Why your code wasn't working
From doctest's testmod documentation:
Test examples in docstrings in functions and classes reachable from
module m (or module __main__ if m is not supplied or is None),
starting with m.__doc__.
So, since you left out the first argument (m), the module __main__ is passed to testmod. So the doctests that were run were the doctests in the module that contain the for loop. You can see this for yourself:
run_tests.py
"""
>>> print 'oops'
oops
"""
# assume I already have a list of all my files to test
for file in all_my_files:
temp_module = __import__(file)
temp_module.test()
If you run your example now (before fixing it) you'll see that you'll get:
Trying:
print 'oops'
Expecting:
oops
ok
1 items passed all tests:
1 tests in sample
1 tests in 1 items.
1 passed and 0 failed.
Test passed.
Clearly showing that the doctests running are those in run_tests.py. The name argument only changes the name that appears in the message (sample).
Essentially I have a script in one file which I would like to import and run as a function in another file. Here is the catch, the contents of the first file CANNOT be written as function definition it just needs to be a plain old script (I'm writing a simulator for my robotics kit so user experience is important). I have no idea how to go about this.
Adam
Anything can be written as a function.
If you additionally need the ability to call your script directly, you just use the __name__ == '__main__' trick:
def my_function():
... code goes here ...
if __name__ == '__main__':
my_function()
Now you can import my_function from the rest of your code, but still execute the file directly since the block at the end will call the function.
Assuming that the code in the file you need to import is a well bounded script - then you can read in as a text variable and use the "execfile" function to create a function from that script.
By well bounded I mean that you understand all the data it needs and you are able to provide all of it from your program.
An alternative would be to use the "system" call, or the subprocess module to call the script as if it was an external program (depending if you need the script output).
A final approach will be to use exec to create a function - see approach 3.
The approach you use determines what you need your other script to do ..
examples :
hello.py (your file you want to run, but can't change):
# Silly example to illustrate a script which does something.
fp = open("hello.txt", "a")
fp.write("Hello World !!!\n")
fp.close()
Three approaches to use hello.py without importing hello.py
import os
print "approach 1 - using system"
os.system("python hello.py")
print "approach 2 - using execfile"
execfile("hello.py", globals(), locals())
print "approach 3 - exec to create a function"
# read script into string and indent
with open("hello.py","r") as hfp:
hsrc = [" " + line for line in hfp]
# insert def line
hsrc.insert(0, "def func_hello():")
# execute our function definition
exec "\n".join( hsrc) in globals(), locals()
# you now have a function called func_hello, which you can call just like a normal function
func_hello()
func_hello()
print "My original script is still running"
Is there a way to write a python doctest string to test a script intended to be launched from the command line (terminal) that doesn't pollute the documentation examples with os.popen calls?
#!/usr/bin/env python
# filename: add
"""
Example:
>>> import os
>>> os.popen('add -n 1 2').read().strip()
'3'
"""
if __name__ == '__main__':
from argparse import ArgumentParser
p = ArgumentParser(description=__doc__.strip())
p.add_argument('-n',type = int, nargs = 2, default = 0,help = 'Numbers to add.')
p.add_argument('--test',action = 'store_true',help = 'Test script.')
a = p.parse_args()
if a.test:
import doctest
doctest.testmod()
if a.n and len(a.n)==2:
print a.n[0]+a.n[1]
Running doctest.testmod() without using popen just causes a test failure because the script is run within a python shell instead of a bash (or DOS) shell.
The advanced python course at LLNL suggests putting scripts in files that are separate from .py modules. But then the doctest strings only test the module, without the arg parsing. And my os.popen() approach pollutes the Examples documentation. Is there a better way?
Just found something looking like the answer you want:
shell-doctest.
doctest is meant to run python code, so you have to do a conversion somewhere. If you are determined to test the commandline interface directly via doctest, one possibility is to do a regexp substitution to __doc__ before you pass it to argparse, to take out the os.popen wrapper:
clean = re.sub(r"^>>> os\.popen\('(.*)'\).*", r"% \1", __doc__)
p = ArgumentParser(description=clean, ...)
(Of course there are all sorts of nicer ways to do that, depending on what you consider "nice").
That'll clean it up for the end user. If you also want it to look cleaner in the source, you can go the other way: Put commandline examples in the docstring and don't use doctest.testmodule(). Run your docstring through doctest.script_from_examples and post-process it to insert the os calls. (Then you'll have to embed it into something so you can test it with run_docstring_examples.) doctest doesn't care if the input is valid python, so you can do the following:
>>> print doctest.script_from_examples("""
Here is a commandline example I want converted:
>>> add -n 3 4
7
""")
# Here is a commandline example I want converted:
add -n 3 4
# Expected:
## 7
This will still expose the python prompt >>> in the help. If this bothers you, you may just have to process the string in both directions.
You can also load the docstring yourself and execute the command, like in this test.
import sys
module = sys.modules[__name__]
docstring = module.__doc__
# search in docstring for certain regex, and check that the following line(s) matches a pattern.
in c code I frequently use printf debugging macros like
#define DPRINT_INT(i) fprintf(stderr,"%s has the value %i at line %i", #i,i, __LINE__)
and then i can do things like
DPRINT_INT(height)
where it will print the variable or things like
DPRINT_INT(from_cm_to_inch(get_average(heights)))
and it will print out the whole expression for the name.
To do this for python, since python doesn't have c-like macros
I pass a string and use inspect to get the calling functions environment to call eval with.
But I don't like passing strings, its ugly and easy to forget(I have it check type and call an exception if it gets passed a non string) and doesn't work as well with the ide.
There isn't any way to extract the variable names and expressions from the python code for the debug function? is there?
In Python we tend to write modules that we can unittest and / or import into the REPL and drive them there if necessary.
If you can unit-test your functions, you can prove their behaviour for any given input. However, if you must write debug statements, you should use debug() from the standard logging module.
For example:
#!/usr/bin/env python
import logging
import inspect
def buggy_fn():
d = 42;
if d != 69:
logging.debug('%s(%d): %s is not what we expected. [%s]',
inspect.currentframe().f_back.f_code.co_filename,
inspect.currentframe().f_back.f_lineno,
'd',
repr(d),
)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
buggy_fn()
Will output...
DEBUG:root:./buggy.py(19): d is not what we expected. [42]
There's a wealth of useful stuff in inspect that may help you out in your hour of need.
I recently faced a problem about combining unit tests and doctests in Python. I worked around this problem in other way, but I still have question about it.
Python's doctest module parses docstrings in a module and run commands following ">>> " at the beginning of each line and compare the output of it and those in docstrings.
I wonder that I could use that comparison method implemented by doctest module when I want. I know that it's possible add doctest to test suite as a test case, but here I want to do it inside a single test case.
It is something like this:
class MyTest(TestCase):
def testIt(self):
# some codes like self.assertEqual(...)
output = StringIO()
with StdoutCollector(output):
# do something that uses stdout
# I want something like this:
doctest.compare_result(output.getvalue(), 'expected output')
# do more things
Because doctest uses some heuristics to compare the outputs like ellipsis.
Would somebody give an idea or suggestions?
See doctest.OutputChecker.check_output()