test casestest cases and cleanup after their execution
In [26]:
class HexNumber(object):
def __init__(self, v):
self._v = int(v)
def __str__(self):
return '0x%x' % self._v
def __int__(self):
return int(self._v)
class Operator(object):
def div(self, a, b):
res = int(a) / int(b)
return res
In [27]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
class TestHexNumber(object):
"""Test Case for HexNumber"""
def test_value(self):
"""Test Unit for printing HEX"""
x = HexNumber(5)
assert str(x) == '0x5'
def test_number(self):
"""Test Unit for conversion to plain python nums"""
x = HexNumber(5.0)
assert int(x) == 5
class TestOperator(object):
"""Test Case for Operator"""
def test_division(self):
"""Test Unit for division of two ints"""
op = Operator()
assert op.div(7, 3) == 2
@raises(ZeroDivisionError)
def test_division_zero(self):
"""Test Unit for division by ZERO"""
op = Operator()
assert op.div(5, 0)
suite = ContextSuite()
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumber))
suite.addTests(TestLoader().loadTestsFromTestClass(TestOperator))
nose.run(suite=suite, argv=[''])
Out[27]:
In [29]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
class TestHexNumberOperatorIntegration(object):
"""Integration Test Case for Operator working with HexNumber"""
def setup(cls):
"""Test Case Fixture, performs before each test unit"""
cls.op = Operator()
def teardown(cls):
"""Test Case Fixture Cleanup, performs after each test unit"""
cls.op = None
def test_hex_division(self):
"""Test Unit for printing HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert res == HexNumber(2), (res, type(res))
suite = ContextSuite()
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumberOperatorIntegration))
nose.run(suite=suite, argv=[''])
Out[29]:
In [32]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
class HexNumber(object):
def __init__(self, v):
self._v = int(v)
def __str__(self):
return '0x%x' % self._v
def __int__(self):
return int(self._v)
class Operator(object):
def div(self, a, b):
res = int(a) / int(b)
return a.__class__(res) # PRESERVE TYPE
class TestHexNumber(object):
"""Test Case for HexNumber"""
def test_value(self):
"""Test Unit for printing HEX"""
x = HexNumber(5)
assert str(x) == '0x5'
def test_number(self):
"""Test Unit for conversion to plain python nums"""
x = HexNumber(5.0)
assert int(x) == 5
class TestOperator(object):
"""Test Case for Operator"""
def test_division(self):
"""Test Unit for division of two ints"""
op = Operator()
assert op.div(7, 3) == 2
@raises(ZeroDivisionError)
def test_division_zero(self):
"""Test Unit for division by ZERO"""
op = Operator()
assert op.div(5, 0)
class TestHexNumberOperatorIntegration(object):
"""Integration Test Case for Operator working with HexNumber"""
def setup(cls):
"""Test Case Fixture, performs before each test unit"""
cls.op = Operator()
def teardown(cls):
"""Test Case Fixture Cleanup, performs after each test unit"""
cls.op = None
def test_hex_division_is_hex(self):
"""Test Unit checking that HEX division gives back and HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert isinstance(res, HexNumber), res.__class__
def test_hex_division(self):
"""Test Unit for printing HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert res == HexNumber(2), res
suite = ContextSuite()
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumber))
suite.addTests(TestLoader().loadTestsFromTestClass(TestOperator))
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumberOperatorIntegration))
nose.run(suite=suite, argv=[''])
Out[32]:
In [33]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
class HexNumber(object):
def __init__(self, v):
self._v = int(v)
def __str__(self):
return '0x%x' % self._v
def __int__(self):
return int(self._v)
def __eq__(self, other):
# PERMIT COMPARISON
return self._v == other._v
class Operator(object):
def div(self, a, b):
res = int(a) / int(b)
return a.__class__(res) # PRESERVE TYPE
class TestHexNumber(object):
"""Test Case for HexNumber"""
def test_value(self):
"""Test Unit for printing HEX"""
x = HexNumber(5)
assert str(x) == '0x5'
def test_number(self):
"""Test Unit for conversion to plain python nums"""
x = HexNumber(5.0)
assert int(x) == 5
class TestOperator(object):
"""Test Case for Operator"""
def test_division(self):
"""Test Unit for division of two ints"""
op = Operator()
assert op.div(7, 3) == 2
@raises(ZeroDivisionError)
def test_division_zero(self):
"""Test Unit for division by ZERO"""
op = Operator()
assert op.div(5, 0)
class TestHexNumberOperatorIntegration(object):
"""Integration Test Case for Operator working with HexNumber"""
def setup(cls):
"""Test Case Fixture, performs before each test unit"""
cls.op = Operator()
def teardown(cls):
"""Test Case Fixture Cleanup, performs after each test unit"""
cls.op = None
def test_hex_division_is_hex(self):
"""Test Unit checking that HEX division gives back and HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert isinstance(res, HexNumber), res.__class__
def test_hex_division(self):
"""Test Unit for printing HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert res == HexNumber(2), res
suite = ContextSuite()
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumber))
suite.addTests(TestLoader().loadTestsFromTestClass(TestOperator))
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumberOperatorIntegration))
nose.run(suite=suite, argv=[''])
Out[33]:
Coverage is the process of identifying all the paths of execution that the Test Suite is not checking.
Aiming at 100% code coverage means that we are sure that out tests passes through all the if brances in our code and all the code we wrote has been run at least once.
Does it mean that we tested everything? No...
Coverage is able to guarantee that we checked everything we wrote, it is not able to measure code that we should have written but didn't missing behaviours won't be reported in coverage.
Coverage can be run by passing --with-coveage and --cover-package=packagename to nose. If we don't provide the --cover-package argument coverage will be reported for all the modules loaded by python.
In [35]:
with open('_opnums.py', 'w') as sourcecode:
sourcecode.write('''
class HexNumber(object):
def __init__(self, v):
self._v = int(v)
def __str__(self):
return '0x%x' % self._v
def __int__(self):
return int(self._v)
def __eq__(self, other):
return self._v == other._v
class Operator(object):
def div(self, a, b):
res = int(a) / int(b)
return a.__class__(res)
''')
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
from _opnums import HexNumber, Operator
class TestHexNumberOperatorIntegration(object):
"""Integration Test Case for Operator working with HexNumber"""
def setup(cls):
"""Test Case Fixture, performs before each test unit"""
cls.op = Operator()
def teardown(cls):
"""Test Case Fixture Cleanup, performs after each test unit"""
cls.op = None
def test_hex_division_is_hex(self):
"""Test Unit checking that HEX division gives back and HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert isinstance(res, HexNumber), res.__class__
def test_hex_division(self):
"""Test Unit for printing HEX"""
x = HexNumber(7)
y = HexNumber(3)
res = self.op.div(x, y)
assert res == HexNumber(2), res
suite = ContextSuite()
suite.addTests(TestLoader().loadTestsFromTestClass(TestHexNumberOperatorIntegration))
nose.run(suite=suite, argv=['testsuite', '--with-coverage', '--cover-erase', '--cover-package=_opnums'])
Out[35]:
You already know that Python web applications are developed according to the WSGI standard so a WSGI server is required to run them. For this reason testing web applications is a bit harder as it requires to simulate the request -> response flow.
Fortunately this is something that is already done for us by the WebTest module which is provided with TurboGears devtools.
WebTest provides an application object with methods that emulate HTTP requests: .get, .post, .put and so on and is able to understand both html and json responses.
Nose provides an easy way to create class fixtures, instead of test case fixtures which are executed before and after each test unit the class fixture are execute before and after the test case itself (so once for all the test units).
This can be useful when fixture perform an heavy operation that is not required to be performed again for each test unit, like creating the test application.
In [36]:
from wsgiref.simple_server import make_server
from tg import expose, TGController, AppConfig
class RootController(TGController):
@expose()
def index(self):
return '''<html>
<head>
<title>Hello to You</title>
</head>
<body>
<h1>Hello World</h1>
</body>
'''
config = AppConfig(minimal=True, root_controller=RootController())
application = config.make_wsgi_app()
In [39]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
from webtest import TestApp
class TestHelloWorldApp(object):
@classmethod
def setup_class(cls):
cls.app = TestApp(application)
def test_hello_world(self):
res = self.app.get('/')
assert 'Hello World' in res
suite = ContextSuite(context=TestHelloWorldApp(),
tests=TestLoader().loadTestsFromTestClass(TestHelloWorldApp))
nose.run(suite=suite, argv=[''])
Out[39]:
In [7]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
from webtest import TestApp
class TestHelloWorldApp(object):
@classmethod
def setup_class(cls):
cls.app = TestApp(application)
def test_hello_world(self):
res = self.app.get('/')
assert res.pyquery('h1').text() == 'Hello World'
def test_title(self):
res = self.app.get('/')
assert res.pyquery('title').text() == 'Hello to You'
suite = ContextSuite(context=TestHelloWorldApp(),
tests=TestLoader().loadTestsFromTestClass(TestHelloWorldApp))
nose.run(suite=suite, argv=[''])
Out[7]:
In [45]:
from wsgiref.simple_server import make_server
from tg import expose, TGController, AppConfig
class RootController(TGController):
@expose()
def index(self):
return '''<html>
<head>
<title>Hello to You</title>
</head>
<body>
<h1>Hello World</h1>
<div>
<form id="form1" action="/submit" method="POST">
<input type="text" name="value"/>
</form>
</div>
</body>'''
@expose('json')
def submit(self, value=None, **kwargs):
return dict(value=value)
config = AppConfig(minimal=True, root_controller=RootController())
application = config.make_wsgi_app()
In [46]:
import nose
from nose.suite import ContextSuite
from nose.loader import TestLoader
from nose.tools import raises
from webtest import TestApp
class TestHelloWorldApp(object):
@classmethod
def setup_class(cls):
cls.app = TestApp(application)
def test_hello_world(self):
res = self.app.get('/')
assert res.pyquery('h1').text() == 'Hello World'
def test_title(self):
res = self.app.get('/')
assert res.pyquery('title').text() == 'Hello to You'
def test_form_submission(self):
page = self.app.get('/')
form = page.forms['form1']
form['value'] = 'prova'
res = form.submit()
assert res.json['value'] == 'prova', res
suite = ContextSuite(context=TestHelloWorldApp(),
tests=TestLoader().loadTestsFromTestClass(TestHelloWorldApp))
nose.run(suite=suite, argv=[''])
Out[46]:
During the previous example the test suite configuration has been performed manually using the ContextSuite class and started with nose.run.
This is something that you usually won't do as it is done by TurboGears and nose for you. To run the test suite it is as simple as running::
$ nosetests -v --with-coverage --cover-erase --cover-package=myapp
Nose itself will look in all files whose name starts with test_[something].py for all the classes which name starts with Test[Something] and will consider them as Test Cases, for each method inside the test case whose name starts with test_[something] they will be threated as Test Units
When quickstarting an application you will notice that there is a tests package inside it.
This package is provided by TurboGears itself and contains the fixture already creates the TestApp instance for you and loads configuration from test.ini instead of development.ini.
Take note that test.ini actually inherits from development.ini and just overwrites some options.
For example for tests by default a sqlalchemy.url = sqlite:///:memory: is used which forces SQLAlchemy to use an in memory database instead of a real one, so that it is created and discarded when the test suite is run.
All your application tests that call a web page should inherit from tests.TestController which ensure:
setup-appself.app object is provided which is a TestApp instance of your TurboGears2 application loaded from test.iniSee tests/functional/test_root:
from nose.tools import ok_
from testapp.tests import TestController
class TestRootController(TestController):
"""Tests for the method in the root controller."""
def test_index(self):
response = self.app.get('/')
msg = 'TurboGears 2 is rapid web application development toolkit '\
'designed to make your life easier.'
ok_(msg in response)
def test_environ(self):
response = self.app.get('/environ.html')
ok_('The keys in the environment are:' in response)
To simulate authentication you can just pass to the .get, .post and so on methods an extra_environ parameter (which is used to add WSGI environ values available in tg.request.environ) name REMOTE_USER.
For example if you want to behave like you are logged as the editor user you just pass:
def test_secc_with_editor(self):
environ = {'REMOTE_USER': 'editor'}
self.app.get('/secc', extra_environ=environ, status=403)
In [ ]: