Combining nosetests with contracts for Python - python

I'm using contracts for Python to specify preconditons/postconditions/invariants. I'm also using doctests for doing unit testing.
I'd like to have all of my doctest unit tests run with contracts enabled, and I'd like to run my tests using nose. Unfortunately, if I run the tests with nose, it does not execute the pre/post/invariant assertions. I put a setup function in each .py file to make sure that contract.checkmod gets called
def setup():
import contract
contract.checkmod(__name__)
I can confirm that this function is being executed by nose before it runs the tests, but the contracts still don't get executed.
On the other hand, if I run the doctest by calling doctest.testmod, the pre/post/inv do get called:
def _test():
import contract
contract.checkmod(__name__)
import doctest
doctest.testmod()
if __name__=='__main__':
_test()
Here's an example of a Python script whose test will succeed if called directly, but failed if called with nose:
import os
def setup():
import contract
contract.checkmod(__name__)
def delete_file(path):
"""Delete a file. File must be present.
>>> import minimock
>>> minimock.mock('os.remove')
>>> minimock.mock('os.path.exists', returns=True)
>>> delete_file('/tmp/myfile.txt')
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
>>> minimock.restore()
pre: os.path.exists(path)
"""
os.remove(path)
if __name__ == '__main__':
setup()
import doctest
doctest.testmod()
When I run the above file standalone, the tests pass:
$ python contracttest.py -v
Trying:
import minimock
Expecting nothing
ok
Trying:
minimock.mock('os.remove')
Expecting nothing
ok
Trying:
minimock.mock('os.path.exists', returns=True)
Expecting nothing
ok
Trying:
delete_file('/tmp/myfile.txt')
Expecting:
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
ok
Trying:
minimock.restore()
Expecting nothing
ok
2 items had no tests:
__main__
__main__.setup
1 items passed all tests:
5 tests in __main__.delete_file
5 tests in 3 items.
5 passed and 0 failed.
Test passed.
Here it is with nose:
$ nosetests --with-doctest contracttest.py
F
======================================================================
FAIL: Doctest: contracttest.delete_file
----------------------------------------------------------------------
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/doctest.py", line 2131, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for contracttest.delete_file
File "/Users/lorin/Desktop/contracttest.py", line 10, in delete_file
----------------------------------------------------------------------
File "/Users/lorin/Desktop/contracttest.py", line 17, in contracttest.delete_file
Failed example:
delete_file('/tmp/myfile.txt')
Expected:
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
Got:
Called os.remove('/tmp/myfile.txt')
----------------------------------------------------------------------
Ran 1 test in 0.055s

Related

Use unittest to run tests from function [duplicate]

I have a file TestProtocol.py that has unittests. I can run that script and get test results for my 30 tests as expected. Now I want to run those tests from another file tester.py that is located in the same directory. Inside tester.py I tried import TestProtocol, but it runs 0 tests.
Then I found the documentation which says I should do something like this:
suite = unittest.TestLoader().discover(".", pattern = "*")
unittest.run(suite)
This should go through all files in the current directory . that match the pattern *, so all tests in all files. Unfortunately it again runs 0 tests.
There is a related QA that suggests to do
import TestProtocol
suite = unittest.findTestCases(TestProtocol)
unittest.run(suite)
but that also does not find any tests.
How do I import and run my tests?
You can try with following
# preferred module name would be test_protol as CamelCase convention are used for class name
import TestProtocol
# try to load all testcases from given module, hope your testcases are extending from unittest.TestCase
suite = unittest.TestLoader().loadTestsFromModule(TestProtocol)
# run all tests with verbosity
unittest.TextTestRunner(verbosity=2).run(suite)
Here is a full example
file 1: test_me.py
# file 1: test_me.py
import unittest
class TestMe(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
if __name__ == '__main__':
unittest.main()
file 2: test_other.py, put this under same directory
# file 2: test_other.py, put this under same directory
import unittest
import test_me
suite = unittest.TestLoader().loadTestsFromModule(test_me)
unittest.TextTestRunner(verbosity=2).run(suite)
run each file, it will show the same result
# python test_me.py - Ran 1 test in 0.000s
# python test_other.py - Ran 1 test in 0.000s

Python3 - output __main__ file prints when running unittests (from actual program, not unittests)

How can I make that my __main__ file prints are outputted, when I run tests? I mean prints from that file, not unittests files prints.
I have this sample structure (all files are in the same directory):
main.py:
import argparse
print('print me?') # no output
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('name')
args = parser.parse_args()
print(args.name) # no output
other.py:
def print_me():
print('ran print_me')
test.py:
import unittest
import sh
import other
class TestMain(unittest.TestCase):
def test_main(self):
print('test_main') # prints it.
sh.python3('main.py', 'test123')
def test_other(self):
print('test_other') # prints it.
other.print_me()
And I run it with python3 -m nose -s or python3 -m unittest, but it makes no difference, prints are not outputted from main.py, only the ones that are defined directly on test file. Here is what I do get:
user#user:~/python-programs/test_main$ python3 -m nose -s
test_main
.test_other
ran print_me
.
----------------------------------------------------------------------
Ran 2 tests in 0.040s
OK
P.S. Of course if I run main.py without using tests, then it prints normally (for example using python shell/interpreter and calling main.py with sh, just like in unittests)
sh.python3 starts new process and its output is not captured by nose. You can redirect the output printing the result from it:
print(sh.python3('main.py', 'test123'))

Running Python's doctest on a file named `types.py`

In both Python 2 and 3 I cannot run doctests in a file named types.py, which is part of a package. This is what I get:
$ cat foo/types.py
def x():
"""do something
>>> x()
1
"""
return 1
$ cp foo/types.py foo/types2.py
$ python -m doctest -v foo/types.py
1 items had no tests:
types
0 tests in 1 items.
0 passed and 0 failed.
Test passed.
$ python -m doctest -v foo/types2.py
Trying:
x()
Expecting:
1
ok
1 items had no tests:
types2
1 items passed all tests:
1 tests in types2.x
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
$ python3 -m doctest -v foo/types.py
37 items had no tests:
types
types.DynamicClassAttribute
types.DynamicClassAttribute.__delete__
types.DynamicClassAttribute.__get__
types.DynamicClassAttribute.__init__
types.DynamicClassAttribute.__set__
types.DynamicClassAttribute.deleter
types.DynamicClassAttribute.getter
types.DynamicClassAttribute.setter
types.SimpleNamespace
types.SimpleNamespace.__delattr__
types.SimpleNamespace.__eq__
types.SimpleNamespace.__ge__
types.SimpleNamespace.__getattribute__
types.SimpleNamespace.__gt__
types.SimpleNamespace.__init__
types.SimpleNamespace.__le__
types.SimpleNamespace.__lt__
types.SimpleNamespace.__ne__
types.SimpleNamespace.__reduce__
types.SimpleNamespace.__repr__
types.SimpleNamespace.__setattr__
types._GeneratorWrapper
types._GeneratorWrapper.__init__
types._GeneratorWrapper.__iter__
types._GeneratorWrapper.__next__
types._GeneratorWrapper.close
types._GeneratorWrapper.cr_await
types._GeneratorWrapper.gi_code
types._GeneratorWrapper.gi_frame
types._GeneratorWrapper.gi_running
types._GeneratorWrapper.send
types._GeneratorWrapper.throw
types._calculate_meta
types.coroutine
types.new_class
types.prepare_class
0 tests in 37 items.
0 passed and 0 failed.
Test passed.
$ python3 -m doctest -v foo/types2.py
Trying:
x()
Expecting:
1
ok
1 items had no tests:
types2
1 items passed all tests:
1 tests in types2.x
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
As you see, all invocations with foo/types2.py work as expected, all invocations with foo/types.py seem to try to load the Python built-in types module.
I'm also not able to fix this by tinkering with PYTHONPATH:
$ PYTHONPATH=.:$PYTHONPATH python -m doctest -v foo/types.py
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 400, in <module>
import UserDict
File "/usr/lib/python2.7/UserDict.py", line 116, in <module>
import _abcoll
File "/usr/lib/python2.7/_abcoll.py", line 70, in <module>
Iterable.register(str)
File "/usr/lib/python2.7/abc.py", line 107, in register
if not isinstance(subclass, (type, types.ClassType)):
AttributeError: 'module' object has no attribute 'ClassType'
Unfortunately, I cannot simply rename foo/types.py.
Is there any possibility to run doctests from this file apart from writing lots of boilerplate code around it?
I don’t think you can use python -m doctest here: the documentation says that it “import[s the module] as a standalone module”, adding
Note that this may not work correctly if the file is part of a package and imports other submodules from that package.
which is a fancy way of saying that it uses the module’s unqualified name. Of course it then conflicts with the standard library module.

python unittest and pytest - can I assign test status to a variable

I am working on a python based testing system, that iterates through a set of python tests and run them one by one (there are unittests and pytests).
Is there a way that my testing system to understand the result of every individual tests and saves it to a dictionary with key [test_name] and value [test_status] for example. I imagine if the result of the test to be assigned to a variable for example:
test_status = "passed"
PS: all of the tests have a main(), that looks like that
# for unittests
def main():
unittest.main()
# for pytests
def main():
os.system("py.test -v {}".format(os.path.abspath(__file__)))
If I understood correctly, you want to actually run pytest or unittests as a command line tool and retrieve the results.
The straightforward way to do this would be to use JUnit xml output and parse it. For example in pytest:
pytest --junitxml=path
Maybe you want to consider using an automation server like Jenkins, that will run unittests and pytest tests separately and then collect the results.
I have found a solution for unittest framework:
The idea is to change the test ouput data to be not on the terminal console, but to a file. One way to do it is to add the following code to the tests:
if __name__ == '__main__':
# terminal command to run a specific test and save its output to a log file
# python [this_module_name] [log_file.log] [*tests]
parser = argparse.ArgumentParser()
parser.add_argument('test_log_file')
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
log_file = sys.argv[1]
# Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone)
sys.argv[1:] = args.unittest_args
with open(log_file, "w") as f:
runner = unittest.TextTestRunner(f)
unittest.main(defaultTest=sys.argv[2:], exit=False, testRunner=runner)
and run it with command like this one:
python my_tests.py log_file.log class_name.test_1 class_name.test_2 ... test_n
Other way is with direct command that looks like this one:
python -m unittest [test_module_name].[test_class_name].[test_name] 2> [log_file_name]
# real command example:
python -m unittest my_tests.class_name.test_1 2> my_test_log_file.log
# real command example with multiple tests:
python -m unittest my_tests.class_name.test_1 my_tests.class_name.test_2 my_tests.class_name.test_3 2> my_test_log_file.log
The final part is to write a method, that reads this log file and get the result of the test. Those log files look something like that:
.FEs
======================================================================
ERROR: test_simulative_error (__main__.SimulativeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unittest_tests.py", line 84, in test_simulative_error
raise ValueError
ValueError
======================================================================
FAIL: test_simulative_fail (__main__.SimulativeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unittest_tests.py", line 81, in test_simulative_fail
assert False
AssertionError
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1, errors=1, skipped=1)
So the final step is to open that log_file and read the first line that gives information how the test/tests have finished and save this info as you want.

import unittest error

This is my first time doing unit testing and i'm trying to run a simple code...
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def test_shuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
# should raise an exception for an immutable sequence
self.assertRaises(TypeError, random.shuffle, (1,2,3))
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)
then i get the error message...
Traceback (most recent call last):
File "/Users/s66/Desktop/unittest.py", line 2, in <module>
import unittest
File "/Users/s66/Desktop/unittest.py", line 4, in <module>
class TestSequenceFunctions(unittest.TestCase):
AttributeError: 'module' object has no attribute 'TestCase'
>>>
How do I fix this?
That's because your script name is called unittest.py. The statement import unittest is importing your script rather than the unittest module, hence the error with the non-existant TestCase attribute.
For more info, see the docs for Module Search Path. In short, when you do an import, built-in modules are first searched followed by directories listed in sys.path. This usually starts with the location of the running script followed by PYTHONPATH and THEN then default modules directory
In your case, since unittest is not a built-in, it found your script (and loaded it) before it could search for other installed modules.
How do I fix this?
Rename your script.
Do you have a module in the directory where you're running your script that is called unittest.py?
When I run your code I get the following output.
test_choice (__main__.TestSequenceFunctions) ... ok
test_sample (__main__.TestSequenceFunctions) ... ERROR
test_shuffle (__main__.TestSequenceFunctions) ... ok
======================================================================
ERROR: test_sample (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 23, in test_sample
with self.assertRaises(ValueError):
TypeError: failUnlessRaises() takes at least 3 arguments (2 given)
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (errors=1)
My answer is not intended for this author, but for people that found this stackoverflow page on Google for the same error message but for a different reason.
I was upgrading from django 1.5 to django 1.10, and the "import unittest" in one of my test files was causing a similar error.
The error was due to the app/tests/init.py file which was importing the errored test file (this was necessary in django 1.5 as the automatic detection of test files didn't exist at that time. Now it is no longer required).
So the solution was to simply empty the init.py file and it solved the issue.
Hope it can help some people as I lost 30mn to 1h on this issue.

Categories