[MERGE] xmo python unit tests

bzr revid: al@openerp.com-20110321135427-fb6w9c3lexsdautw
This commit is contained in:
Antony Lesuisse 2011-03-21 14:54:27 +01:00
commit d40743d0e3
10 changed files with 324 additions and 21 deletions

View File

@ -118,29 +118,39 @@ class Menu(openerpweb.Controller):
@openerpweb.jsonrequest
def load(self, req):
m = req.session.model('ir.ui.menu')
return {'data': self.do_load(req)}
def do_load(self, req):
""" Loads all menu items (all applications and their sub-menus).
:param req: A request object, with an OpenERP session attribute
:type req: < session -> OpenERPSession >
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
Menus = req.session.model('ir.ui.menu')
# menus are loaded fully unlike a regular tree view, cause there are
# less than 512 items
menu_ids = m.search([])
menu_items = m.read(menu_ids, ['name', 'sequence', 'parent_id'])
menu_ids = Menus.search([])
menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'])
menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
menu_items.append(menu_root)
# make a tree using parent_id
for i in menu_items:
i['children'] = []
d = dict([(i["id"], i) for i in menu_items])
for i in menu_items:
if not i['parent_id']:
pid = False
else:
pid = i['parent_id'][0]
if pid in d:
d[pid]['children'].append(i)
# sort by sequence a tree using parent_id
for i in menu_items:
i['children'].sort(key=lambda x:x["sequence"])
menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
for menu_item in menu_items:
if not menu_item['parent_id']: continue
parent = menu_item['parent_id'][0]
if parent in menu_items_map:
menu_items_map[parent].setdefault(
'children', []).append(menu_item)
return {'data': menu_root}
# sort by sequence a tree using parent_id
for menu_item in menu_items:
menu_item.setdefault('children', []).sort(
key=lambda x:x["sequence"])
return menu_root
@openerpweb.jsonrequest
def action(self, req, menu_id):

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
import mock
import unittest2
import base.controllers.main
import openerpweb.openerpweb
class Placeholder(object):
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
class LoadTest(unittest2.TestCase):
def setUp(self):
self.menu = base.controllers.main.Menu()
self.menus_mock = mock.Mock()
self.request = Placeholder(
session=openerpweb.openerpweb.OpenERPSession(
model_factory=lambda _session, _name: self.menus_mock))
def tearDown(self):
del self.request
del self.menus_mock
del self.menu
def test_empty(self):
self.menus_mock.search = mock.Mock(return_value=[])
self.menus_mock.read = mock.Mock(return_value=[])
root = self.menu.do_load(self.request)
self.menus_mock.search.assert_called_with([])
self.menus_mock.read.assert_called_with(
[], ['name', 'sequence', 'parent_id'])
self.assertListEqual(
root['children'],
[])
def test_applications_sort(self):
self.menus_mock.search = mock.Mock(return_value=[1, 2, 3])
self.menus_mock.read = mock.Mock(return_value=[
{'id': 2, 'sequence': 3, 'parent_id': [False, '']},
{'id': 3, 'sequence': 2, 'parent_id': [False, '']},
{'id': 1, 'sequence': 1, 'parent_id': [False, '']},
])
root = self.menu.do_load(self.request)
self.menus_mock.read.assert_called_with(
[1, 2, 3], ['name', 'sequence', 'parent_id'])
self.assertEqual(
root['children'],
[{
'id': 1, 'sequence': 1,
'parent_id': [False, ''], 'children': []
}, {
'id': 3, 'sequence': 2,
'parent_id': [False, ''], 'children': []
}, {
'id': 2, 'sequence': 3,
'parent_id': [False, ''], 'children': []
}])
def test_deep(self):
self.menus_mock.search = mock.Mock(return_value=[1, 2, 3, 4])
self.menus_mock.read = mock.Mock(return_value=[
{'id': 1, 'sequence': 1, 'parent_id': [False, '']},
{'id': 2, 'sequence': 2, 'parent_id': [1, '']},
{'id': 3, 'sequence': 1, 'parent_id': [2, '']},
{'id': 4, 'sequence': 2, 'parent_id': [2, '']},
])
root = self.menu.do_load(self.request)
self.assertEqual(
root['children'],
[{
'id': 1,
'sequence': 1,
'parent_id': [False, ''],
'children': [{
'id': 2,
'sequence': 2,
'parent_id': [1, ''],
'children': [{
'id': 3,
'sequence': 1,
'parent_id': [2, ''],
'children': []
}, {
'id': 4,
'sequence': 2,
'parent_id': [2, ''],
'children': []
}]
}]
}]
)

View File

@ -0,0 +1,9 @@
<addon name>
+-- __openerp__.py
+-- controllers/
+-- static/
+-- openerp/
+-- css/
+-- img/
+-- js/
+-- tests/

View File

@ -1,7 +1,91 @@
Developing OpenERP Web Addons
=============================
* Structure of an addon
Structure
---------
.. literalinclude:: addon-structure.txt
``__openerp__.py``
The addon's descriptor, contains the following information:
``name: str``
The addon name, in plain, readable english
``version: str``
The addon version, following `Semantic Versioning`_ rules
``depends: [str]``
A list of addons this addon needs to work correctly. ``base`` is
an implied dependency if the list is empty.
``css: [str]``
An ordered list of CSS files this addon provides and needs. The
file paths are relative to the addon's root. Because the Web
Client *may* perform concatenations and other various
optimizations on CSS files, the order is important.
``js: [str]``
An ordered list of Javascript files this addon provides and needs
(including dependencies files). As with CSS files, the order is
important as the Web Client *may* perform contatenations and
minimizations of files.
``active: bool``
Whether this addon should be enabled by default any time it is
found, or whether it will be enabled through other means (on a
by-need or by-installation basis for instance).
``controllers/``
All of the Python controllers and JSON-RPC endpoints.
``static/``
The static files directory, may be served via a separate web server.
The third-party dependencies should be bundled in it (each in their
own directory).
``static/openerp/``
Sub-tree for all the addon's own static files.
``static/openerp/{css,js,img}``
Location for (respectively) the addon's static CSS files, its JS
files and its various image resources.
``tests/``
The directories in which all tests for the addon are located.
.. _addons-testing:
Testing
-------
Python
++++++
OpenERP Web uses unittest2_ for its testing needs. We selected
unittest2 rather than unittest_ for the following reasons:
* autodiscovery_ (similar to nose, via the ``unit2``
CLI utility) and `pluggable test discovery`_.
* `new and improved assertions`_ (with improvements in type-specific
inequality reportings) including `pluggable custom types equality
assertions`_
* neveral new APIs, most notably `assertRaises context manager`_,
`cleanup function registration`_, `test skipping`_ and `class- and
module-level setup and teardown`_
* finally, unittest2 is a backport of Python 3's unittest. We might as
well get used to it.
To run tests on addons (from the root directory of OpenERP Web) is as
simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
test an addon which does not live in the ``addons`` directory, simply
replace ``addons`` by the directory in which your own addon lives.
.. note:: unittest2 is entirely compatible with nose_ (or the
other way around). If you want to use nose as your test
runner (due to its addons for instance) you can simply install it
and run ``nosetests addons`` instead of the ``unit2`` command,
the result should be exactly the same.
* Addons lifecycle (loading, execution, events, ...)
* Python-side
@ -16,3 +100,54 @@ Developing OpenERP Web Addons
* Javascript public APIs
* QWeb templates description?
* OpenERP Web modules (from OpenERP modules)
.. [#] the ``-s`` parameter tells ``unit2`` to start trying to
find tests in the provided directory (here we're testing
addons). However a side-effect of that is to set the
``PYTHONPATH`` there as well, so it will fail to find (and
import) ``openerpweb``.
The ``-t`` parameter lets us set the ``PYTHONPATH``
independently, but it doesn't accept multiple values and here
we really want to have both ``.`` and ``addons`` on the
``PYTHONPATH``.
The solution is to set the ``PYTHONPATH`` to ``.`` on start,
and the ``start-directory`` to ``addons``. This results in a
correct ``PYTHONPATH`` within ``unit2``.
.. _unittest:
http://docs.python.org/library/unittest.html
.. _unittest2:
http://www.voidspace.org.uk/python/articles/unittest2.shtml
.. _autodiscovery:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
.. _pluggable test discovery:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
.. _new and improved assertions:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
.. _pluggable custom types equality assertions:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
.. _assertRaises context manager:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
.. _cleanup function registration:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
.. _test skipping:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
.. _class- and module-level setup and teardown:
http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
.. _Semantic Versioning:
http://semver.org/
.. _nose:
http://somethingaboutorange.com/mrl/projects/nose/1.0.0/

View File

@ -6,4 +6,25 @@ Contributing to OpenERP Web
* QWeb code documentation/description
* Documentation of the OpenERP APIs and choices taken based on that?
* Style guide and coding conventions (PEP8? More)
* Test frameworks for Python and JS?
* Test frameworks in JS?
Testing
-------
Python
++++++
Testing for the OpenERP Web core is similar to :ref:`testing addons
<addons-testing>`: the tests live in ``openerpweb.tests``, unittest2_
is the testing framework and tests can be run via either unittest2
(``unit2 discover``) or via nose_ (``nosetests``).
Tests for the OpenERP Web core can also be run using ``setup.py
test``.
.. _unittest2:
http://www.voidspace.org.uk/python/articles/unittest2.shtml
.. _nose:
http://somethingaboutorange.com/mrl/projects/nose/1.0.0/

View File

@ -39,13 +39,15 @@ class OpenERPModel(object):
class OpenERPSession(object):
def __init__(self, server='127.0.0.1', port=8069):
def __init__(self, server='127.0.0.1', port=8069,
model_factory=OpenERPModel):
self._server = server
self._port = port
self._db = False
self._uid = False
self._login = False
self._password = False
self.model_factory = model_factory
def proxy(self, service):
s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5)
@ -69,7 +71,7 @@ class OpenERPSession(object):
return r
def model(self, model):
return OpenERPModel(self, model)
return self.model_factory(self, model)
#----------------------------------------------------------
# OpenERP Web RequestHandler

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
import mock
import unittest2
import openerpweb.openerpweb
class OpenERPModelTest(unittest2.TestCase):
def test_rpc_call(self):
session = mock.Mock(['execute'])
Model = openerpweb.openerpweb.OpenERPModel(
session, 'a.b')
Model.search([('field', 'op', 'value')], {'key': 'value'})
session.execute.assert_called_once_with(
'a.b', 'search', [('field', 'op', 'value')], {'key': 'value'})
session.execute.reset_mock()
Model.read([42])
session.execute.assert_called_once_with(
'a.b', 'read', [42])

View File

@ -59,6 +59,11 @@ setup(
"simplejson >= 2.0.9",
"python-dateutil >= 1.4.1",
],
tests_require=[
'unittest2',
'mock',
],
test_suite = 'unittest2.collector',
zip_safe=False,
packages=[
'addons',