Running python scripts within a subpackage of my package - python

Having some trouble figuring out the correct, python 2.x preferred way to do relative imports so that I can keep test scripts together in one subpackage, and have those test scripts be able to test my library.
$ farm\testpad\testpad.py
Traceback (most recent call last):
File "C:\farm\testpad\testpad.py", line 4, in <module>
from ..animals.dog import dog
ValueError: Attempted relative import in non-package
$ python -m farm\testpad\testpad
C:\Python27\python.exe: No module named farm\testpad\testpad
In the following example, what do I need to modify to do what I want? Please, and thanks for any help given.
e.g.
Package structure:
farm/
__init__.py # empty
animals/
__init__.py # empty
dog.py
testpad/
__init__.py # empty
testpad.py
dog.py
import sys
import os
class dog():
def __init__(self, name):
self.name = name
def speak(self):
print "woof"
testpad.py
import os
import sys
from ..animals.dog import dog
# create a dog and test its speak action
def testpad():
d = dog("Allen")
d.speak()
if __name__ == "__main__":
testpad()

Three things:
always run your tests scripts from root of your project. This simple rule does not harm anything and will simplify your scenario
prefer absolute imports
-m option for python expects module in dotted form
Applying this to your code
Modify testpad.py
import os
import sys
from farm.animals.dog import dog
# Create a dog and test its speak action
def testpad():
d = dog("Allen")
d.speak()
if __name__ == "__main__":
testpad()
Call it from python -m <module>
$ python -m farm.testpad.testpad
woof
Bonus - testing from nose
For my testing I use following pattern
keep all testing code in project subdir called test or better tests
use nose testing framework
Being in root of your project
Install nose:
$ pip install nose
What creates a command nosetests
Reorganize your code
$ mkdir tests
$ mv farm/testpad/testpad.py tests/test_animals.py
$ rm -rf farm/testpad/
Run the tests (currently just one)
The simples way, wich does the work, but tries to keep the output minimal:
$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
As you see, nose is discovering test cases on his own (searching for whatever starts with test)
Make it a bit more verbose:
$ nosetests -v
test_animals.testpad ... ok
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
Now you know, what tests were run.
To see captured outut, use -s switch:
$ nosetests -vs
test_animals.testpad ... woof
ok
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
Add more tests to test_animals.py
import os
import sys
# create a dog and test its speak action
def test_dog():
from farm.animals.dog import dog
d = dog("Allen")
d.speak()
def test_cat():
from farm.animals.dog import cat
c = cat("Micy")
d.speak()
def test_rabbit():
from farm.animals.dog import rabbit
r = rabbit()
r.speak()
Test it:
$ nosetests -vs
test_animals.test_dog ... woof
ok
test_animals.test_cat ... ERROR
test_animals.test_rabbit ... ERROR
======================================================================
ERROR: test_animals.test_cat
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/javl/Envs/so/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/home/javl/sandbox/so/testpad/tests/test_animals.py", line 12, in test_cat
from farm.animals.dog import cat
ImportError: cannot import name cat
======================================================================
ERROR: test_animals.test_rabbit
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/javl/Envs/so/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/home/javl/sandbox/so/testpad/tests/test_animals.py", line 17, in test_rabbit
from farm.animals.dog import rabbit
ImportError: cannot import name rabbit
----------------------------------------------------------------------
Ran 3 tests in 0.004s
FAILED (errors=2)
Add --pdb switch to jump to debugger (if you install ipdbplugin, you might use --ipdb):
$ nosetests -vs --pdb
test_animals.test_dog ... woof
ok
test_animals.test_cat ... > /home/javl/sandbox/so/testpad/tests/test_animals.py(12)test_cat()
-> from farm.animals.dog import cat
(Pdb) l
7 from farm.animals.dog import dog
8 d = dog("Allen")
9 d.speak()
10
11 def test_cat():
12 -> from farm.animals.dog import cat
13 c = cat("Micy")
14 d.speak()
15
16 def test_rabbit():
17 from farm.animals.dog import rabbit
(Pdb)
Use h or find some tutorial for debugger, it is great tool
Real test cases shall assert that behaviour is as expected.
Assert printed values are as expected
As you are printing to stdout, we can capture the output using mocking (in Python 2.x you have to install it, in Python 3.x you from unittest import mock):
$ pip install mock
And modify your test_animals.py:
from mock import patch
from StringIO import StringIO
# create a dog and test its speak action
#patch("sys.stdout", new_callable=StringIO)
def test_dog(mock_stdout):
from farm.animals.dog import Dog
d = Dog("Allen")
d.speak()
assert mock_stdout.getvalue() == "woof\n"
#patch("sys.stdout", new_callable=StringIO)
def test_cat(mock_stdout):
from farm.animals.cat import Cat
c = Cat("Micy")
c.speak()
assert mock_stdout.getvalue() == "mnau\n"
#patch("sys.stdout", new_callable=StringIO)
def test_rabbit(mock_stdout):
from farm.animals.rabbit import Rabbit
r = Rabbit("BB")
r.speak()
assert mock_stdout.getvalue() == "Playboy, Playboy\n"
Final test case of my taste test_mytaste.py
def test_dog():
from farm.animals.dog import Dog
d = Dog("Allen")
sound = d.speak()
assert sound == "woof", "A dog shall say `woof`"
def test_cat():
from farm.animals.cat import Cat
c = Cat("Micy")
sound = c.speak()
assert sound == "mnau", "A cat shall say `mnau`"
def test_rabbit():
from farm.animals.rabbit import Rabbit
r = Rabbit("BB")
sound = r.speak()
assert sound == "Playboy, Playboy", "A Rabbit shall say ..."
This requires you refactor your code (class names starting uppercase, speak method not printing but returning the sound).
In fact, you may start writing your code from test cases and adding tested modules later on, this often leads to nicer design as you start thinking of real use from the very beginning.
Selective call of test cases
nose is great in searching for test cases (generally whatever starts with test), but sometime you might focus on particular test or use differently named python file. You can tell nose to use just one test file:
$ nosetests -vs tests/test_mytaste.py
test_mytaste.test_dog ... ok
test_mytaste.test_cat ... ok
test_mytaste.test_rabbit ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
or even target nose to run specific test from it:
$ nosetests -vs tests/test_mytaste.py:test_dog
test_mytaste.test_dog ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Related

Test not being executed from command line

Below test works as expected when I execute from PyCharm but when I attempt to run python TestData.py from command line I receive :
Traceback (most recent call last):
File "TestData.py", line 3, in <module>
from service.DataAccessor import Data
ModuleNotFoundError: No module named 'service'
When I try python -m unittest
I receive:
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Both commands are run from project directory.
Project structure is:
project/service
- DataAccessor.py
project/test
- TestData.py
Code:
TestData.py :
import unittest
from service.DataAccessor import Data
class TestData(unittest.TestCase):
def test_get_data(self):
print(Data.getDf(self))
self.assertEqual(3, 3)
if __name__ == '__main__':
unittest.main()
DataAccessor.py:
import pandas as pd
class Data():
def getDf(self):
df = pd.read_csv("./data.csv")
return df
Coming from Java background I'm not 100% sure why this works but adding an empty __init__.py to the dirs: test & service and executed python -m unittest test.TestData executes the test.

How can I improve code coverage of Python3

With unittest and Coverage.py,
def add_one(num: int):
num = num + 1
return num
from unittest import TestCase
from add_one import add_one
class TestAddOne(TestCase):
def test_add_one(self):
self.assertEqual(add_one(0), 1)
self.assertNotEqual(add_one(0), 2)
and here is the coverage:
How can I test the whole file?
Assuming that your test file is called test_one.py run this command in the same directory:
coverage run -m unittest test_one.py && coverage report
Result should look similar to this:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Name Stmts Miss Cover
---------------------------------
add_one.py 3 0 100%
test_one.py 6 0 100%
---------------------------------
TOTAL 9 0 100%
You never call the test_add_one method.
Note how the function definition is executed, but not the body. To run your test, add a __main__ check and a TestSuite/TextTestRunner (https://docs.python.org/3/library/unittest.html)
from unittest import TestCase, TestSuite, TextTestRunner
from add_one import add_one
class TestAddOne(TestCase):
def test_add_one(self):
self.assertEqual(add_one(0), 1)
self.assertNotEqual(add_one(0), 2)
if __name__ == "__main__":
suite = TestSuite()
suite.addTest(TestAddOne("test_add_one"))
TextTestRunner().run(suite)
The result of
coverage run <file.py>
coverage html
# OR
coverage report -m
is all lines tested.

Django and tests in docfiles

I'm having a small problem with my test suite with Django.
I'm working on a Python package that can run in both Django and Plone (http://pypi.python.org/pypi/jquery.pyproxy).
All the tests are written as doctests, either in the Python code or in separate docfiles (for example the README.txt).
I can have those tests running fine but Django just do not count them:
[vincent ~/buildouts/tests/django_pyproxy]> bin/django test pyproxy
...
Creating test database for alias 'default'...
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
But if I had some failing test, it will appear correctly:
[vincent ~/buildouts/tests/django_pyproxy]> bin/django test pyproxy
...
Failed example:
1+1
Expected nothing
Got:
2
**********************************************************************
1 items had failures:
1 of 44 in README.rst
***Test Failed*** 1 failures.
Creating test database for alias 'default'...
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
This is how my test suite is declared right now:
import os
import doctest
from unittest import TestSuite
from jquery.pyproxy import base, utils
OPTIONFLAGS = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE)
__test__ = {
'base': doctest.testmod(
m=base,
optionflags=OPTIONFLAGS),
'utils': doctest.testmod(
m=utils,
optionflags=OPTIONFLAGS),
'readme': doctest.testfile(
"../../../README.rst",
optionflags=OPTIONFLAGS),
'django': doctest.testfile(
"django.txt",
optionflags=OPTIONFLAGS),
}
I guess I'm doing something wrong when declaring the test suite but I don't have a clue what it is exactly.
Thanks for your help,
Vincent
I finally solved the problem with the suite() method:
import os
import doctest
from django.utils import unittest
from jquery.pyproxy import base, utils
OPTIONFLAGS = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE)
testmods = {'base': base,
'utils': utils}
testfiles = {'readme': '../../../README.rst',
'django': 'django.txt'}
def suite():
return unittest.TestSuite(
[doctest.DocTestSuite(mod, optionflags = OPTIONFLAGS)
for mod in testmods.values()] + \
[doctest.DocFileSuite(f, optionflags = OPTIONFLAGS)
for f in testfiles.values()])
Apparently the problem when calling doctest.testfile or doctest.testmod is that the tests are directly ran.
Using DocTestSuite/DocFileSuite builds the list and then the test runner runs them.

Doctest and relative imports

I'm having trouble using doctest with relative imports. The simple solution is just to get rid of the relative imports. Are there any others?
Say I have a package called example containing 2 files:
example/__init__.py
"""
This package is entirely useless.
>>> arnold = Aardvark()
>>> arnold.talk()
I am an aardvark.
"""
from .A import Aardvark
if __name__ == "__main__":
import doctest
doctest.testmod()
example/A.py
class Aardvark(object):
def talk(self):
print("I am an aardvark.")
If I now attempt
python example/__init__.py
then I get the error
Traceback (most recent call last):
File "example/__init__.py", line 8, in <module>
from .A import Aardvark
ValueError: Attempted relative import in non-package
Create another file my_doctest_runner.py:
if __name__ == "__main__":
import doctest
import example
doctest.testmod(example)
Execute my_doctest_runner.py to run doctests in example/__init__.py:
$ python2.7 my_doctest_runner.py
**********************************************************************
File "/tmp/example/__init__.py", line 4, in example
Failed example:
arnold.talk()
Expected:
I am an aaardvark.
Got:
I am an aardvark.
**********************************************************************
1 items had failures:
1 of 2 in example
***Test Failed*** 1 failures.
Pytest's --doctest-modules flag takes care of relative imports:
$ ls example/
A.py __init__.py
$ pytest --doctest-modules example
==================== test session starts ====================
...
example/__init__.py . [100%]
===================== 1 passed in 0.03s =====================
Just do
from A import Aardvark

Running Tests From a Module

I am attempting to run some unit tests in python from what I believe is a module. I have a directory structure like
TestSuite.py
UnitTests
|__init__.py
|TestConvertStringToNumber.py
In testsuite.py I have
import unittest
import UnitTests
class TestSuite:
def __init__(self):
pass
print "Starting testting"
suite = unittest.TestLoader().loadTestsFromModule(UnitTests)
unittest.TextTestRunner(verbosity=1).run(suite)
Which looks to kick off the testing okay but it doesn't pick up any of the test in TestConvertNumberToString.py. In that class I have a set of functions which start with 'test'.
What should I be doing such that running python TestSuite.py actually kicks off all of my tests in UnitTests?
Here is some code which will run all the unit tests in a directory:
#!/usr/bin/env python
import unittest
import sys
import os
unit_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
os.chdir(unit_dir)
suite = unittest.TestSuite()
for filename in os.listdir('.'):
if filename.endswith('.py') and filename.startswith('test_'):
modname = filename[:-2]
module = __import__(modname)
suite.addTest(unittest.TestLoader().loadTestsFromModule(module))
unittest.TextTestRunner(verbosity=2).run(suite)
If you call it testsuite.py, then you would run it like this:
testsuite.py UnitTests
Using Twisted's "trial" test runner, you can get rid of TestSuite.py, and just do:
$ trial UnitTests.TestConvertStringToNumber
on the command line; or, better yet, just
$ trial UnitTests
to discover and run all tests in the package.

Categories

Resources