Professional Documents
Culture Documents
4/#*) 1 -.$*).
-*% / '4*0/
# $(+*-/ .4./ (
/)- '$--$ .
-( 2*-&.
)"$)"
#)" .
$$
*0( )//$*)
+#$)3 (*0' .
$./-$0/$*)
$/ *! #$./*-4
# # ' !*-(/
&" $)./''/$*)
)/-4 +*$)/.
)$/ / ./$)"
# .$.
$3/0- .
*&$)"
)-$*.
$$$
*1 -"
./$)" +*'$4
- /$)" *-/*-.
//$ ( /#*.
'.. ( /#*
./-/ ( /#*.
0)/$*)' +-*"-(($)"
) -/*-.
#
-!*-() . ) *+/$($5/$*).
$1
/ ./-0/0- .
-*!$'$)"
- - '$./ ) $. /
( /0+' ) .'*/.
(*$5/$*)
44
#$ 1$)" 5 -* *+4 2$/# /# 0 - +-*/**'
)/ -1$ 2 2$/# $/*- /$)) -
'$)" ) -#$/ /0-
-$/ ' .. * (*-
$)"' $.+/# -
*)/ 3/ ()" -.
)$/ / ./$)"
- &$)" ) 2.
/. ) /# - - ./$'' + *+' 2#* *)/ #1 +*'$4 *! / ./
$)" /# $- +-*% /. *2 /# +0-+*. *! /#$. **& $. )*/ /* *)1$) 4*0 /* %0(+ $)
) ./-/ 0)$/ / ./$)"
! 4*0 ) /* *)1$)
.0"" ./ 4*0 ./-/ 4 - $)"
*0/ /# ) !$/. *! / ./-$1 ) 1 '*+( )/ -$/$)" * /#/ $. )*/ / ./ $.
.. )/$''4 0. ' .. . /# - . )* 24 /* *)'0.$1 '4 +-*1 /#/ $/ 2*-&.
#$. . /$*) 2$'' *1 - /# 4/#*) /**'. 4*0 ) 0. /* *)./-0/ "- / .0$/ *!
/ ./. '' /'& *0/ #*2 4*0 ) 0/$'$. /# ( /* )#) 4*0- .*2- (&$)"
$/ -*&.*'$ ) - "- ..$*) !-
6.1
The basics
*)/--4 /* 2#/ 4*0 (4 '$ 1 /# 2-$/$)" ) -0))$)" *! 0)$/ / ./. $. - ''4
.$(+' $) 4/#*)
/. )*/ $)/-0.$1 *- $.-0+/$1 ) $/. "*$)" /* # '+ 4*0 ) */# 1 '*+ -. '*/ $) ($)/$)$)" 4*0- .*2-
*0- / ./. .#*0' ./*- $).$ tests .0(*0' *! 4*0- ++'$/$*) *- '$--4
#$. ''*2. 4*0 /* .#$+ /# / ./. . +-/ *! 4*0- (*0' .* /#/ /# 4 ) -0) *- 0. 4 )4*) 1 ) *) 4*0- .*2- $. $)./'' 2$/#*0/ ) ..-$'4 0.$)"
/# .*0- +&" #$. '.* +- 1 )/. /# ( !-*( $)" $)./'' 4 ($./& $)
/*+' 1 ' tests (*0'
#1 $) 4*0- (*0' /- #$. ( ). /#/ /# / ./. *1 -$)" /# * *! mylib/foo
bar.py
.#*0' $).$
mylib/tests/test_foobar.py
#$. $. /# (*./ .$(+' 0)$/ / ./ /#/ ) 2-$// ) * -0) $/ 4*0 .$(+'4 ) /*
'* /#
test_true.py !$'
) -0) /#
test_true !0)/$*)
!$) 2$/#$)
test_true.py !$'
0. /# !*''*2$)" *0/+0/
$ nosetests -v
test_true.test_true ... ok
OK
=========================================================
FAIL: test_true.test_false
runTest
self.test(*self.arg)
File "/home/jd/test_true.py", line 5, in test_false
assert False
AssertionError
--------------------------------------------------------Ran 2 tests in 0.003s
FAILED (failures=1)
$ nosetests -v
test_complicated.test_key ... FAIL
==========================================================
FAIL: test_complicated.test_key
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/nose/case.py", line 197, in
runTest
self.test(*self.arg)
File "/home/jd/test_complicated.py", line 4, in test_key
assert a == b
AssertionError
FAILED (failures=1)
unittest +&"
*( . $) #)4
/ +-*1$ . /**'. /#/ 2$'' # '+
*1 -$)" '' *! /#/ ) "** ) 2. $. /#/ unittest $. +-/ *! /# 4/#*) ./)-
'$--4
Warning
unittest has been largely improved starting with Python2.7, so if you are supporting
earlier versions of Python you may want to use its backport named unittest2. If you
need to support Python2.6, you can then use the following snippet to import the correct
module for any Python versions at runtime:
try:
import unittest2 as unittest
except ImportError:
import unittest
! 2 - 2-$/ /# +- 1$*0. 3(+' 0.$)" unittest /#$. $. 2#/ $/ 2$'' '**& '$&
import unittest
class TestKey(unittest.TestCase):
def test_key(self):
a = ['a', 'b']
b = ['b']
self.assertEqual(a, b)
. 4*0 ) . /# $(+' ( )//$*) $.)/ (0# (*- *(+'$/ '' 4*0 #1 /*
* $. - / '.. /#/ $)# -$/. !-*( unittest.TestCase ) 2-$/ ( /#* /#/
-0). / ./
)./ *! 0.$)" assert 2 - '4 *) ( /#* +-*1$ 4 unittest.
TestCase /#/ +-*1$
$ nosetests -v
test_key (test_complicated.TestKey) ... FAIL
=========================================================
FAIL: test_key (test_complicated.TestKey)
Traceback (most recent call last):
File "/home/jd/Source/python-book/test_complicated.py", line 7, in
test_key
self.assertEqual(a, b)
AssertionError: Lists differ: ['a', 'b'] != ['b']
- ['a', 'b']
+ ['b']
FAILED (failures=1)
. 4*0 ) . /# *0/+0/ $. (0# (*- 0. !0' ) .. -/$*) --*- $. ./$'' -$.
) /# / ./ $. ./$'' $)" !$' 0/ / ' ./ 2 #1 - ' $)!*-(/$*) *0/ 2#4 $/.
!$'$)" 2#$# ) # '+ 0. /* !$3 /# +-*' ( #$. $. 2#4 4*0 .#*0' !$)$/ '4 ) 1 0.
assert 2#
) 2-$/$)" / ./ . . )4*) 2#* /-$ . /* #& 4*0- * ) ).
0+ !$'$)" / ./ 2$'' !$)$/ '4 /#)& 4*0 !*- #1$)" )*/ 0. assert ) #1$)"
/# - 4 +-*1$$)" #$(# - 2$/# 0""$)" $)!*-(/$*) -$"#/ 24
unittest
/
/
- /# ( ''
fail(msg) (
/#*
#$. ) *)1 )$ )/ 2# ) 4*0 &)*2 /#/ +-/$0'- +-/ *! 4*0- * 2$'' !
$)$/ '4 -$. ) --*- $! 3 0/ 0/ /# - $.)/ +-/$0'- .. -/$*) /* # & !*-
3(+' $'$)" / ./
import unittest
class TestFail(unittest.TestCase):
def test_range(self):
for x in range(5):
if x > 4:
self.fail("Range returned a too big value: %d" % x)
/. .*( /$( . 0. !0' .&$+ / ./ $! $/ )/ -0) !*- 3(+' 4*0 (4 2$.# /*
-0) / ./ *)$/$*)''4 . *) /# +- . ) *- . ) *! +-/$0'- '$--4 *
/#/ ) 4*0 ) -$. /#
unittest.SkipTest
3 +/$*) # ) /# / ./ $. -$.
/#
unittest.skip
*-/*-
try:
import mylib
except ImportError:
mylib = None
class TestSkipped(unittest.TestCase):
@unittest.skip("Do not run this")
def test_fail(self):
self.fail("This should not be run")
def test_skip_at_runtime(self):
if True:
self.skipTest("Finally I don't want to run it")
OK (skipped=3)
Tip
As you may have noticed in Example6.3, the unittest module provides a way to execute a Python module that contains tests. It is less convenient than using nosetests,
as it does not discover test files on its own, but it can still be useful for running a particular
test module.
/#/ -
unittest +-*1$
#. ) ''
3(+' .$)" setUp 2$/# unittest
import unittest
class TestMe(unittest.TestCase):
def setUp(self):
self.list = [1, 2, 3]
def test_length(self):
self.list.append(4)
self.assertEqual(len(self.list), 4)
def test_has_one(self):
self.assertEqual(len(self.list), 3)
self.assertIn(1, self.list)
) /#$. . setUp $. '' !*- -0))$)" test_length ) !*- -0))$)" test_
has_one
/ ) - ''4 #)4 /* - / *% /. /#/ - 2*-& 2$/# 0-$)" #
# / ./
/* . !$3/0- . .
/$*)
Tip
When using nosetests, you often might want to run only one particular test.
You can select which test you want to run by passing it as an argument the
syntax is:
path.to.your.module:ClassOfYourTest.test_method.
6.2
Fixtures
# / ./ ) - . /
. /#
)*/ +-/
*! /# ./)- '$--4 +-*1$ . ) .4 ( #)$.( !*- - /$)" !$3/0- '.. . )
*% /. .0# . /#
useFixture (
/#*
#
fixtures (*0'
Variable 0.
fixtures.Environment
0+*) / ./ 3$/
3(+' .$)" fixtures.EnvironmentVariable
import fixtures
import os
class TestEnviron(fixtures.TestWithFixtures):
def test_environ(self):
fixture = self.useFixture(
fixtures.EnvironmentVariable("FOOBAR", "42"))
self.assertEqual(os.environ.get("FOOBAR"), "42")
def test_environ_no_fixture(self):
self.assertEqual(os.environ.get("FOOBAR"), None)
# ) 4*0 ) $ )/$!4 *((*) +// -). '$& /# . $/. "** $ /* - / !$3
/0- /#/ 4*0 ) - 0. *1 - '' 4*0- / ./ . . #$. "- /'4 .$(+'$!$ . /# '*"$ )
.#*2. 3/'4 2#/ 4*0 - / ./$)" ) $) 2#/ ()) -
Note
If youre wondering why the base class unittest.TestCase isnt used in the examples
in this section, its because fixtures.TestWithFixtures inherits from it.
6.3
Mocking
*& *% /. - .$(0'/ *% /. /#/ ($($ /# #1$*0- *! - ' ++'$/$*)
*% /. 0/ $) +-/$0'- ) *)/-*'' 24. # . -
.+ $''4 0. !0' $) - /
$)" )1$-*)( )/. /#/ .-$ +- $. '4 /# *)$/$*). !*- 2#$# 4*0 2*0' '$& /*
/ ./ *
! 4*0 - 2-$/$)" ) '$ )/ $/. '$& '4 $(+*..$' *- / ' ./ 3/- ( '4 *(+'$
/ /* .+2) /# . -1 - ) / ./ $/ /#-*0"# '' . )-$*. (&$)" $/ - /0-)
1 -4 +*..$' 1'0
/. .+ $''4 $$0'/ /* / ./ !*- '' !$'0- . )-$*.
(0# .$(+' - *+/$*) $. /* 0$' . / *! (*& *% /. /#/ (* ' /# . +-/$0'. )-$*. ) /* 0. /# ( . )1$-*)( )/ !*- / ./$)" 4*0- *
# ./)- '$--4 !*- - /$)" (*& *% /. $) 4/#*) $.
mock
/-/$)" 2$/#
print("hello world!")
...
>>> m.some_method.side_effect = print_hello
>>> m.some_method()
hello world!
>>> def print_hello():
...
print("hello world!")
...
return 43
...
>>> m.some_method.side_effect = print_hello
>>> m.some_method()
hello world!
43
>>> m.some_method.call_count
3
1 ) 0.$)" %0./ /#$. . / *! ! /0- . 4*0 .#*0' ' /* ($($ '*/ *! 4*0- $)/ -)'
*% /. $) *- - /* !& 1-$*0. / . )-$*.
*& 0. . /# /$*).. -/$*) +// -) /#$. ( ). /#/ *) 4*0- / ./ #. -0) 4*0
2$'' #1 /* # & /#/ /# /$*). 4*0 - (*&$)" 2 - *-- /'4 3 0/
3(+' # &$)" ( /#* ''.
>>> import mock
>>> m = mock.Mock()
>>> m.some_method('foo', 'bar')
<Mock name='mock.some_method()' id='26144272'>
>>> m.some_method.assert_called_once_with('foo', 'bar')
>>> m.some_method.assert_called_once_with('foo', mock.ANY)
>>> m.some_method.assert_called_once_with('foo', 'baz')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in
assert_called_once_with
return self.assert_called_with(*args, **kwargs)
File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in
assert_called_with
raise AssertionError(msg)
AssertionError: Expected call: some_method('foo', 'baz')
Actual call: some_method('foo', 'bar')
. 4*0 ) . $/. .4 )*0"# /* +.. (*& *% / /* )4 +-/ *! 4*0- * )
/* # & '/ - $! /# * #. ) '' 2$/# 2#/ 1 - -"0( )/ $/ 2. .0++*.
/* #1
! 4*0 *)/ &)*2 2#/ -"0( )/. (4 #1 ) +.. 4*0 ) 0.
mock.ANY . 1'0
*( /$( . 4*0 (4 ) /* +/# .*( !0)/$*) ( /#* *- *% / !-*( ) 3/ -
)' (*0' mock +-*1$ . . / *! +/#$)" !0)/$*). /* /#/ )
3(+' .$)" mock.patch
>>> import mock
>>> import os
>>> def fake_os_unlink(path):
...
raise IOError("Testing!")
...
>>> with mock.patch('os.unlink', fake_os_unlink):
...
os.unlink('foobar')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in fake_os_unlink
IOError: Testing!
$/# /#
mock.patch (
import requests
import unittest
import mock
class WhereIsPythonError(Exception):
pass
def is_python_still_a_programming_language():
try:
r = requests.get("http://python.org")
except IOError:
pass
else:
if r.status_code == 200:
return 'Python is a programming language' in r.content
raise WhereIsPythonError("Something bad happened")
def raise_get(url):
raise IOError("Unable to fetch url %s" % url)
class TestPython(unittest.TestCase):
@mock.patch('requests.get', get_fake_get(
@mock.patch('requests.get', get_fake_get(
200, 'Python is no more a programming language'))
def test_python_is_not(self):
self.assertFalse(is_python_still_a_programming_language())
@mock.patch('requests.get', get_fake_get(
404, 'Whatever'))
def test_bad_status_code(self):
self.assertRaises(WhereIsPythonError,
is_python_still_a_programming_language)
@mock.patch('requests.get', raise_get)
def test_ioerror(self):
self.assertRaises(WhereIsPythonError,
is_python_still_a_programming_language)
6.4
Scenarios
--*-#)'$)" / ./ 2$/#
0)# *! $ - )/ *% /. /#/ /-$"" - /#/ --*- *- 4*0 (4 2)/ /* -0) ) )/$/ ./ .0$/ "$)./ $ - )/ -$1 -.
#$. './ . $. *) /#/ 2 # 1$'4 - '$ *) $) $'*( / - $'*( / - +-*1$ .
) ./-/ '.. /#/ 2 '' /# ./*-"
)4 -$1 - ) $(+' ( )/ /#$. .
./-/ '.. ) - "$./ - $/. '! /* *( -$1 - # .*2- '*. /# *)!$"
0- ./*-" -$1 - 2# ) - ,0$- ) 0. . /# $(+' ( )/ ./*-"
/* ./**- - /-$ 1 /
) /#$. . 2#/ $. ) $. '.. *! 0)$/ / ./. /#/ -0). "$)./
# -$1 - ( )$)" "$)./ # $(+' ( )//$*) *! /#$. ./*-"
/* .0/#/ /# 4 *)!*-( /* 2#/ /# '' -. 3+ /
# )/0-' 24 *! *$)" /#$. $. /* 0. ($3$) '.. . *) *) .$ 4*0 2*0' #1
'.. 2$/# 0)$/ / ./. ) *) /# */# - .$ '.. 2$/# /# .+ $!$ -$1 - 0."
. /0+
import unittest
class MongoDBBaseTest(unittest.TestCase):
def setUp(self):
self.connection = connect_to_mongodb()
class MySQLBaseTest(unittest.TestCase):
def setUp(self):
self.connection = connect_to_mysql()
class TestDatabase(unittest.TestCase):
def test_connected(self):
self.assertTrue(self.connection.is_connected())
)!*-/0)/ '4 $) /# '*)" -0) /#$. ( /#* $. !- !-*( *)1 )$ )/ *- .''
// - / #)$,0 * . 3$./ 0.$)" /#
testscenarios +&"
class WhereIsPythonError(Exception):
pass
def is_python_still_a_programming_language():
r = requests.get("http://python.org")
if r.status_code == 200:
return 'Python is a programming language' in r.content
raise WhereIsPythonError("Something bad happened")
m.content = content
def fake_get(url):
return m
return fake_get
class TestPythonErrorCode(testscenarios.TestWithScenarios):
scenarios = [
('Not found', dict(status=404)),
('Client error', dict(status=400)),
('Server error', dict(status=500)),
]
def test_python_status_code_handling(self):
with mock.patch('requests.get',
get_fake_get(
self.status,
'Python is a programming language for sure')):
self.assertRaises(WhereIsPythonError,
is_python_still_a_programming_language)
1 ) /#*0"# *)'4 *) / ./ . (. /* !$) testscenarios -0). /# / ./ /#/$( . 0. 2 #1 !$) /#-
. )-$*.
ok
test_python_status_code_handling (test_scenario.TestPythonErrorCode) ...
ok
test_python_status_code_handling (test_scenario.TestPythonErrorCode) ...
ok
OK
. 4*0 ) . '' 2 ) /* *)./-0/ /# . )-$* '$./ $. /0+' '$./ /#/ *).$./.
*! /# . )-$* )( . !$-./ -"0( )/ ) . . *) -"0( )/ /# $/$*)-4 *!
//-$0/ . /* /* /# / ./ '.. !*- /#$. . )-$*
class TestPythonErrorCode(testscenarios.TestWithScenarios):
scenarios = [
('MongoDB', dict(driver=storage.MongoDBStorage())),
('SQL', dict(driver=storage.SQLStorage())),
('File', dict(driver=storage.FileStorage())),
]
def test_storage(self):
self.assertTrue(self.driver.store({'foo': 'bar'}))
def test_fetch(self):
self.assertEqual(self.driver.fetch('foo'), 'bar')
Note
If you wonder why there is no need to use the base class unittest.TestCase in the
previous examples, its because testscenarios.TestWithScenarios inherits from
it.
6.5
nosetests $. /* *0/+0/ /#
''*2. !*- )0( - *! $)/ - ./$)" /#$)". .0# . ""- "/$)" / ./ - .0'/. *- /*
- *- ) -#$1 / ./ -0). /
0))$)" / ./ 0.$)" subunit $. .$(+'
)*0"#
# *0/+0/ *! /#$. *(() $. $)-4 / .* 0)' .. 4*0 #1 /# $'$/4 /* .$"#/
- /# .00)$/ +-*/**' $/ 2*0')/ $)/ - ./$)" /* - +-*0 $/. *0/+0/ $- /'4
# - *2 1 -
subunit
found)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Not
found) ... ok
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Client
error)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Client
error) ... ok
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Server
error)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Server
error) ... ok
OK
*2 /#$. $. .*( /#$)" /#/ 2 ) 0) -./) 4*0 .#*0' - *")$5 /# / ./ .0$/
2$/# . )-$*. !-*( /$*) /# - /**'. 2*-/# ( )/$*)$)" $)'0
subunit2
/#
discover -"0(
found)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Not
found) ... ok
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Client
error)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Client
error) ... ok
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Server
error)
test_scenario.TestPythonErrorCode.test_python_status_code_handling(Server
error) ... ok
OK
*0 ) '$./ / ./. -/# - /#) -0))$)" /# ( 4 +..$)" /# -"0( )/ --list *
1$ 2 /# - .0'/. 4*0 ) 0.
subunit-ls
Tip
You can also load a list of tests that you want to run rather than running all tests by
using the --load-list option.
testrepository
+&" $. $)
/* #)'
PASSED (id=0)
$ testr failing
PASSED (id=0)
$ testr last
Ran 3 tests in 0.001s
PASSED (id=0)
$ testr slowest
Test id
Runtime (s)
----------------------------------------------
-----------
test_python_status_code_handling(Not found)
0.000
test_python_status_code_handling(Server error)
0.000
test_python_status_code_handling(Client error)
0.000
$ testr stats
runs=1
) /# .00)$/ ./- ( *! / ./. #. ) -0) ) '* $).$ / ./- +*.$/*-4 $/ $.
+*..$' /* ()$+0'/ $/ .$'4 0.$)" /#
testr *(()
1$*0.'4 /#$. $. / $*0. /* * 4 #) # /$( 4*0 2)/ /* -0) / ./.
)./
4*0 .#*0' / # testr #*2 $/ .#*0' -0) 4*0- / ./. .* /#/ $/ ) '* /# - .0'/.
$/. '! #$. ) *(+'$.# 4 $/$)" /#
.testr.conf !$'
/ /# -**/ *! 4*0-
+-*% /
3(+' .testr.conf !$'
[DEFAULT]
test_command=python -m subunit.run discover . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
run
# !$-./ '$) test_command $. /# *) /#/ $. /# (*./ $)/ - ./$)" *2 '' /#/ 2
) /* * /* '* / ./. $)/* / ./- +*.$/*-4 ) + -!*-( /# ( $. /* -0) testr
run
Note
If youre accustomed to running nosetests, testr run is now the equivalent command.
2* */# - *+/$*). )' 0. /* -0) /# / ./. $) +-'' ' #$. $. .$(+'
'' 4*0 ) /* * $. /#
)*0"# /* *
run --parallel
--load-list /tmp/tmpiMq5Q1
--load-list /tmp/tmp7hYEkP
--load-list /tmp/tmpP_9zBc
--load-list /tmp/tmpTejc5J
) - /# #** testr -0). /# / ./ '$./$)" *+ -/$*) .+'$/. /# / ./ '$./ $)/* . 1 -'
.0'$./. ) - / . . +-/ 4/#*) +-* .. /* -0) # .0'$./ *! / ./ 4
!0'/ /# )0( - *! .0'$./. $. ,0' /* /# )0( - *! . $) /# (#$) $)"
0. *0 ) *1 --$ /# )0( - *! +-* .. . /#/ 4 $)" /#
!'"
$ testr run --parallel --concurrency=2
--concurrency
/#
6.6
setup.py
*1 - $) /# ) 3/ . /$*)
Coverage
coverage
--with-coverage
Stmts
Miss
Cover
100%
152
20
87%
ceilometer.publisher
12
75%
32-34
ceilometer.sample
31
87%
81-84
ceilometer.transformer
15
80%
26-32, 35
ceilometer.transformer.accumulator
17
100%
ceilometer.transformer.conversions
59
100%
888
393
56%
ceilometer
ceilometer.pipeline
Missing
TOTAL
OK
$)" /#
0/ 4*0 ) * // - ) (& *1 -" " ) -/ )$
- +*-/. $(+'4
/#
--cover-html
!'" ) /#
cover
2$'' +*+0'/ 2$/# +" . # +" 2$'' .#*2 4*0 2#$# +-/. *! 4*0.*0- * 2 - *- 2 - )*/ -0)
R_MIN_PERCENTAGE
#$. 2$'' 0/*(/$''4 -0) 4*0- / ./ .0$/ 2$/# *1 -" ) " ) -/ )
-
+*-/ $) /#
cover $-
/*-4
*0 .#*0' /# ) 0. /#$. $)!*-(/$*) /* *).*'$/ 4*0- / ./ .0$/ ) / ./. !*)4 * /#/ $. 0-- )/'4 )*/ $)" -0) #$. $. $(+*-/)/ $/ !$'$// . '/ - +-*% /
($)/ )) ) $)- . . 4*0- * . *1 -'' ,0'$/4
6.7
*3 $(. /* 0/*(/ ) ./)-$5 #*2 / ./. - -0) $) 4/#*) * /#/ )
$/ +-*1$ . 1 -4/#$)" ) /* -0) ) )/$- / ./ .0$/ $) ' ) 1$-/0' )1$-*)
( )/ 2#$' '.* $)./''$)" 4*0- ++'$/$*) /* # & /#/ /# $)./''/$*) 2*-&. !$)
!*- 0.$)" /*3 4*0 ) /* +-*1$ *)!$"0-/$*) !$' #$. !$' $. )( tox.
ini ) .#*0'
py !$'
$ touch tox.ini
2$'' -0) /# *(() nosetests 2$'' '$& '4 !$' .$) 2 *)/ #1
nosetests $)
[testenv]
deps=nose
commands=nosetests
-0) /# *(() nosetests 2#$# 2$'' 3 0/ '' *! *0- 0)$/ / ./. 1$*0.'4 2
($"#/ 2)/ /* (*- + ) )$ . 4*0 ) '$./ /# ( $) /# +. *)!$"0-/$*)
*+/$*) 0/ 4*0 ) '.* 0. /#
-rfile
+- /* ()" 4*0- . /0++4 !$' 4*0 &)*2 /#/ $/ - . /# + ) )$ . !-*(
!$' '' requirements.txt
/ $. /# - !*- "** $ /* / '' /*3 /* 0. /#/ !$'
/**
[testenv]
deps=nose
-rrequirements.txt
commands=nosetests
#
[testenv] .
()" 4 tox 0/ . ( )/$*) +- 1$*0.'4 /*3 ) ()" (0'/$+' 4/#*)
1$-/0' )1$-*)( )/. $) $/. +*..$' /* -0) *0- / ./. 0) - 4/#*) 1 -.$*)
*/# - /#) /# !0'/ *) 4 +..$)" /#
-e !'" /* tox
% tox -e py26
GLOB sdist-make: /home/jd/project/setup.py
py26 create: /home/jd/project/.tox/py26
py26 installdeps: nose
py26 inst: /home/jd/project/.tox/dist/rebuildd-1.zip
py26 runtests: commands[0] | nosetests
.......
---------------------------------------------------------
OK
____________________ summary _____________________
py26: commands succeeded
congratulations :)
[testenv:py27]
commands=pytest
py27
-e py27 0/ /#
*(()
-0) $)./
[testenv:py21]
basepython=python2.1
[testenv]
deps=nose
commands=nosetests
# ) tox $. '0)# 2$/#*0/ )4 !0-/# - -"0( )/. '' !*0- )1$-*)( )/. '$./
2$'' - / +*+0'/ 2$/# /# + ) )$ . ) /# ++'$/$*) ) /# ) /#
*(() nosetests 2$'' -0)
) '.* 0.
tox /* $)/
flake8 . $.0..
$) /$*)
[tox]
envlist=py26,py27,py33,pypy,pep8
[testenv]
deps=nose
commands=nosetests
[testenv:pep8]
deps=flake8
commands=flake8
6.8
Testing policy
'.* 3/- ( '4 $(+*-/)/ # - - /** ()4 +-*% /. /#/ #1 / ./ * 2#$#
'4. -*0) 0/ 2#$# !$'. /* -0) !*- .*( - .*)
#$' /#$. /*+$ $. )*/ ./-$/'4 '$($/ /* 4/#*)
*).$ - $/ $(+*-/)/ )*0"# /*
(+#.$5 # - 4*0 .#*0' #1 5 -* /*' -) +*'$4 *) 0)/ ./ * * *
.#*0' ( -" 0)' .. /# - $. +-*+ - . / *! 0)$/ / ./. /* *1 - $/
# ($)$(0( /#/ 4*0 .#*0' $( !*- $. /* .0- /#/ # *! /# *(($/. 4*0
+0.# +.. '' /# / ./. 1$)" ) 0/*(/ 24 /* * /#/ $. 1 ) // -
*- 3(+' + )/& - '$ . *) .+ $!$ 2*-&!'*2 . *) --$/ )&$). )
00' # *(($/ +0.# "* . /#-*0"# /# * - 1$ 2 .4./ ( +-*1$ 4 --$/
) 00' $. $) #-" *! -0))$)" . / *! / ./$)" %*. "$)./ $/ 0.$)" )&$). )&$).
-0). /# 0)$/ / ./$)" ) 1-$*0. #$"# -' 1 ' !0)/$*)' / ./. !*- # +-*% / #$.
).0- . /#/ /# .0($// +/# . +.. '' / ./. * - 1$ 2$)" 4 *0+' *!
1 '*+ -. (& . .0- /#/ '' * /#/ $. *(($// #. ..*$/ 0)$/ / ./.
0)!*-/0)/ /#/ /#$. *) +*./+0.# $/. ./$'' !)/./$ 24 /* /-& - "- ..$*).
-1$. .0++*-/. '' .$")$!$)/ 4/#*) 1 -.$*). *0/ *! /# *3 ) $/. +*..$' /*
0./*($5 $/ /* #$"# "- ) 4*01 /$1/ -1$. *) 4*0- +-*% / 1$ /# $ $)/ -! $)" !$' $. .$(+' .travis.yml * . /# %* !*- 4*0
3(+' .travis.yml 3(+' !$'
language: python
python:
- "2.7"
- "3.3"
# command to install dependencies
install: "pip install -r requirements.txt --use-mirrors"
# command to run tests
script: nosetests
# - 1 - 4*0- * $. #*./ /# . 4. $/ $. '24. +*..$' /* $( !*- .*( .*-/
*! 0/*(/$ / ./$)" *! 4*0- .*2- ) /* (& .0- /#/ 4*0 - "*$)" !*-2-
2$/# 4*0- +-*% / )*/ "*$)" &2- 4 $)" (*- 0".
6.9
*0 (4 #1 '- 4 0. *) *! * -/. +-*"-(. 2$/#*0/ &)*2$)" # $.
(*)" */# - /#$)". /# *-$"$)' 0/#*- *! /# 5- $./-$0/ 1 -.$*) *)/-*'
.4./ ( *4 # $. $./$)"0$.# #)*'*"$./ / '*0 -1$ . 2# - #
2*-&. *) + )/& * -/ #. 2-$// ) '*/ *! /# 4/#*) /**'. .-$ $) /#$.
**& .0# . !$3/0- . / ./. )-$*. / ./- +*.$/*-4 ) 1 ) +4/#*).00)$/
/#$)& $/. ) )"$) -$)" /- * *).$ -$)" /# '$& '$#** *! !$'
0- .'$++$)" /#-*0"# /* +-*0/$*) 0) / / /# *./ *! ) 0) / /
!$'0- *! /#/ *(+*) )/ /# .$5 ) *# .$*) *! /# / ( *$)" /#
2*-& & + )/& *)/-$0/*-. )0) +*'$4 $. 1 -4
#- /* 2*-& 2$/# /# - . .* ()4 + *+' #1 *+$)$*). ) -''4
.+ &$)" /# - .#*0' .*( 0/*(/ # & . +-/ *! ')$)" $)
/-0)& /#/ /# * 2$'' * 2#/ $/ $. $)/ ) /* * ) /#/ 2#/ $/ $. $)
/ ) /* * $. 2#/ $. ) ) /#/ .+ &. /* - ,0$-$)" !0)/$*)'
/ ./. /#/ ($"#/ $) $ - )/ * . . )$/ / ./. - "- / !*- .+
) +$))$)" *2) *-) - . .
/#$)& $/. *& /* 1-4 /# ') /2 )
./4' . *! / ./$)" . '*)" . /# - $. / ./$)"
# - /# *./ *! / ./$)" $. 1 -4 #$"# ) /# - /0-). - 1 -4 '*2
/#$)&
$/. !$) /* (& ) $)!*-( $.$*) )*/ /* / ./ 0/ /#/. - '/$1 '4
-- .$/0/$*) (*./ /#$)". ) / ./ !$-'4 # +'4 ) /# ) !$/ *!
/#$)" --*-. -'4 $. 0.0''4 ,0$/ #$"#
#/ - /# ./ ./-/ "$ . /* +0/ $) +' 2# ) 2-$/$)" 4/#*) *
$) *- - /* (& / ./$)" .$ - ) $(+-*1 $/. ,0'$/4
+-/ *0/ *) -). *)/ * (0'/$+' /#$)". $) *) +' /#$. (& .
- 0.
+0- !0)/$*)' ++-*# 2# ) 4*0 ) " $) .$)"' ( /#* $/# - '
0'/ .*( /#$)" *- #)" .*( .// 0/ 2# - +*..$' 1*$ *$)"
*/# #/ 24 4*0 ) / ./ '' *! /# '0'/$)" #1$*0- 2$/#*0/ '
$)" 2$/# .// #)" . .0# . 2-$/$)" /* /. /'&$)" /* )
. -1 - / # ) !$/ 2*-&. /# */# - 24 -*0) /** 4*0 ) - +'
/# '0'/$*) '*"$ !*- / ./. /* +-*1*& *-) - . #1$*0- ) / /
1$ (*&. / ./ *0' . /#/ /#
$ROOT/$PACKAGE/tests
0/
* %0./ *) !*-
1. " $ROOT/$PACKAGE/$SUBPACKAGE/tests
$/#$) / ./.
* ) ($--*- /# ./-0/0- *! /# - ./ *! /# .*0- /-
$ROOT/$PACKAGE/foo.py
2*0' / ./ $) $ROOT/$PACKAGE/tests/tes
t_foo.py
3 +/ + -
#+. / ./.0$/ '*/ ./. !0)/$*) $) /# /*+ ' 1 ' __init__ #$. + -
($/. .$'4 /#$)" /# / ./. !*- .('' !**/+-$)/ $)./''/$*).
#/ - /# /**'. /#/ )
4/#*)
'
-
/# *)/$)0 3+).$*) *! +-'' ' +$'$/$ . $) ) 2 (#$) .
+#*) . )*2 3$./$)" 0)$/ / ./ $)/ -)'
. - )/ *+/$($. !*+-'' ' 2*-&'*. 4 /- ( .0'/ 2*-& $. $( $- /'4 / /#$.
(*- *(+' 3 .# 0'$)" .0++*-/ ' .. 0"'4 .*'0/$*) !*- /# +-*' (.
/#/ '.. ) (*0' .*+ . /0+ $( /
!$)$)" .*( 24 /* *).*'$/ /# '-" 1-$ /4 *! !-( 2*-&. 2 #1
/*4 $/ 2*0' "- / /* ' /* " / *).*'$/ 1$ 2 -*.. (0'
/$+' +-*% /. !*- $)/ "-/$*) / ./$)" /#/ #1 $ - )/ / ./ -0)) -.
$) 0.