Running python scripts within a subpackage of my p

2019-03-20 14:13发布

问题:

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()

回答1:

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