=============
Writing tests
=============
Overview
========
IdeaLoom makes use of both backend and front-end testing technologies in order to
conduct tests. However, historically, the project suffered from the lack of
adoption of modern testing patterns such as TDD_ or BDD_. As a result, it did
not become developer culture to create tests for every new business logic added.
Thankfully, the testing pipeline is fully developed for TDD_ based testing to
be written by developers.
Front-end
=========
IdeaLoom takes advantage of modern javascript testing frameworks in order to run
front-end tests. The front-end test runner is the Mocha_ framework. It is
understood that Mocha was chosen solely because it allows for the test reporter to
be set to `Nyan Cat`_ colors. Mocha is initalized on IdeaLoom start-up in
`assembl/templates/tests/index.jinja2`.
Firstly, Mocha is loaded in the `tests/index.jinja2` by:
.. code-block:: html
And then instantiated and initialized, Mocha is initalized as such:
.. code-block:: html
The `tests/specs.js` is the complete test file, built by Gulp_, described below. Mocha
allows for any style of tests to be written. It is up to the developer to decide how many
suites they wish to write. Currently, these are the test suites (specs) written are:
- Context
* Testing the :js:class:`Context` module
- Langstring
* Testing the front-end langstring logic
- Models
* Testing ``Backbone.Model`` logic
- Objects
* Testing javascript ``Object``\s that are hosted in the ``/js/app/objects/`` directory
- Routes
* Testing the ``app.router`` module
- Utils
* Testing the modules in the ``/js/app/utils`` directory
- Views
* Testing the ``Backbone.View`` objects that are housed in the ``/js/app/views/`` directory
All tests exist in the ``/static/js/app/tests/`` directory with the pattern::
testName.spec.js
Mocha Example
-------------
Mocha allows for nested spec descriptions (test suites). An example of a synchronous Mocha test
is:
.. code-block:: javascript
describe("Test Spec name followed by callback that defines the suite", function(){
//The specific test
it("Name of the test case followed by function that is the test itself", function(){
//..A well thought out test case with an assestion
});
});
As most test cases include a setUp and a tearDown, below is a complete example a single test with
setUp and tearDown:
.. code-block:: javascript
describe("Test Spec name followed by callback that defines the suite", function(){
var globalVar;
//Run before each test case
beforeEach(function(){
globalVar = 'nyan';
});
//Run after each test case
afterEach(function(){
globalVar = null;
});
//The specific test
it("Name of the test case followed by function that is the test itself", function(){
//..A well thought out test case with an assestion
assert.isEqual(globalVar, 'nyan');
});
});
TODO
^^^^
Mocha also allows for asynchronous testing, specifically promisified test cases. As IdeaLoom
heavily uses Bluebird_ promises, it would be ideal to use an assertion library that supports
assertion. Mocha already allows for asynchronous testing (refer to Mocha_ documentation).
A well known and compatible promise-based assertion library is the `Chai as Promised`_, which
should be added to IdeaLoom's package.json once a developer writes asynchronous tests.
Assertion
---------
Javascript allows for many different kinds of assertions. One of the popular packages used by
developers (in 2014) was Chai_. Chai allows for different styles of assertions.
They include BDD style assertions, which has been sprinkled throughout the currently written specs
.. code-block:: javascript
var expect = require('chai').expect,
value = 1;
expect(value).to.be.a('Number');
expect(value).to.be.ok;
Or TDD style assertions, which are closer to the traditional J-unit style assertions.
.. code-block:: javascript
var assert = require('chai').assert,
value = 1;
assert.isNumber(value, "Value is a number");
assert.isOK(value, "This should pass");
Mocking
-------
This is not yet implemented. However, the recommended mocking libary is Sinon_. Sinon allows
for natural and simple stubs to be created of core objects which can then be easily
tested. Refer to the Sinon_ documentation for stubs on how to convert a jQuery ajax method
into a stub. This can be used heavily to override an XmlHttpRequest to a server. This, along
with fixtures can be used to test the front-end functionality.
Fixtures
--------
Front-end fixtures currently exist in the ``/js/app/tests/fixtures`` directory. Currently they
are hand-written json files that describe different objects as represented from the back-end.
This is fragile and inefficient. Developers are currently working on adding fixtures from the
backend as json files to be consumed by front-end tests.
Gulp
----
IdeaLoom's front-end tests are divided into multiples files in the ``js/app/tests`` directory.
However, they are served to a single file. This is thanks to a the gulp process ``build:test``
which is used to bundle the tests. This means that Browserify_ can be used in the testing
process as well.
How to Run
----------
Front-end tests can be run for each discussion in the ``/test`` API point. For example, the mocha
tests can be run on the browser at the location::
https://demo.idealoom.org/about_idealoom/test
Currently, there is no command-line tool to run the tests on the CLI. This is currently in the works
to be added.
Back-end
========
Back-end testing is carried out via `py\.test`_ Python library. It acts as both the test runner and
the fixture generator. Pytest allows for a level of flexibility in writing tests that the regular
Python unittest_ library simply doesn't have. It allows to write tests similarly to unittest allows,
with a TestCase class created with multiple `test_method`\s written inside.
IdeaLoom uses py.test's fixture's in order to mock objects for testing.
Fixtures
--------
IdeaLoom fixtures are defined in the `/assembl/tests/fixtures/` directory. Fixtures can be divided into
multiple files for ease of use.
The fixtures are read into the py.test test-runner by the use of the ``conftest.py``. All fixtures
are loaded into the runner by importing them. Here is an example of loading `langstring` fixtures:
.. code-block:: python
from assembl.tests.fixtures.langstring import *
When creating new fixture files, you **must** include them in the ``conftest.py``, otherwise they
will not be available to the test runner.
How To Write A Fixture
^^^^^^^^^^^^^^^^^^^^^^
Writing test fixtures in IdeaLoom is extremely simple. Within the fixture folder, in either a new file or an
existing one, simply create a function with the py.test fixture decorator, like such:
.. code-block:: python
@pytest.fixture(scope='function')
def your_new_fixture(dependent_fixture):
pass
In the example above, the ``dependent_fixture`` is a previously written fixture. The fixture can exist
in either the same file or another; it matters not. The fixtures are not run from that particular file.
They are all loaded into the conftest namespace.
Core Fixtures
^^^^^^^^^^^^^
IdeaLoom has several core fixtures that are important to note, in order to run them.
- default_db_data
* A fixture that is rarely explicitly called in a test, however, is vital for successfully
running back-end tests. It is responsible for bootstrapping the test database, creating the
tables necessary based on the latest models, building the relationships and constraints, etc.
Without this fixture, back-end tests could not be done.
- test_session
* Arguably the most important fixture to know. ``test_session`` is the database session
fixture. It can be used to query the database, push new models, etc. It is an SQLAlchemy
session maker. A ``test_session`` depends on a ``default_db_data``
- test_server
* A uWSGI server fixture that refers to an IdeaLoom instance
- test_app
* An IdeaLoom instance fixture, built on WebTest's TestApp_ testing tool. This fixture
builds on ``test_app_no_perm`` and gives the ``admin_user`` fixture administrative permissions,
based on Pyramid's authorization policy. User this fixture to make API calls, as it best
mocks an IdeaLoom interface
- admin_user
* A user fixture that has administrative priveledges
- test_adminuser_webrequest
* A Pyramid GET request to "/", built on WebTest's TestRequest_, that includes an ``admin_user``
as its ``authenticated_user_id``.
- browser
* A browser fixture that is built on top of Splinter_ for integration testing. This specific fixture
is bound to the `phantom\.js`_ driver. To create a different, use this fixture as a template for
creating other drivers. Splinter's has explicit documentation of different driver usages.
For more information regarding testing a Pyramid application, see the `Pyramid Documentation`_ on testing.
IdeaLoom uses WebTest_ to conduct it's integration testing of a Pyramid application.
Authentication Token From Email
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is a simple tutorial for sections that are not documented well. In order to test authentication tokens sent
to newly registered users, or users who have forgotten their passwords, the following steps will simplify your job.
It is hardly an automated process, but it is useful to have this knowledge.
First, uncomment the following line from your `local.ini` file
.. code:: ini
# mail.port = 8025
This will enable you to use a debugging server. Open a new terminal.
.. code:: sh
source venv/bin/activate
python -m smtpd -n -c DebuggingServer
Outgoing emails will be viewable in the terminal. To test it, run a local assembl instance.
.. code:: sh
source venv/bin/activate
circusd circusd.conf
circusctl start pserve webpack
Enter the login page, and choose the option "Forgot my password". Enter your username or email, assuming that you already
have an account in your local Assembl instance. Submit. You should now see the outbound email in your `DebuggingServer` terminal.
In the plaintext section of the email, copy the URL sent to change the password. This URL must, in fact, be altered. Here is a the mapping::
"=" => line-break
"%3D" => "="
Replacing those characters, you should have a URL like the following
`http://localhost:6543/debate/sandbox/do_password_change/037672017097193710TvqNU6m5zof0wwK7hpwZkn14-0K9cH8DeHEDtiEy7ASpLdY=`
Integration
===========
TODO
.. _Mocha: https://mochajs.org/
.. _Chai: http://chaijs.com/
.. _Pytest: http://pytest.org/latest/
.. _Gulp: http://gulpjs.com/
.. _Sinon: http://sinonjs.org/
.. _Bluebird: http://bluebirdjs.com/docs/getting-started.html
.. _Browserify: http://browserify.org/
.. _TDD: http://agiledata.org/essays/tdd.html
.. _BDD: https://en.wikipedia.org/wiki/Behavior-driven_development
.. _`py\.test`: http://pytest.org/latest/
.. _`Nyan Cat`: http://www.nyan.cat/
.. _`Chai as Promised`: https://github.com/domenic/chai-as-promised
.. _unittest: https://docs.python.org/2.7/library/unittest.html
.. _TestApp: http://docs.pylonsproject.org/projects/webtest/en/latest/testapp.html
.. _TestRequest: http://docs.pylonsproject.org/projects/webtest/en/latest/api.html#webtest-app-testrequest
.. _WebTest: http://docs.pylonsproject.org/projects/webtest/en/latest/
.. _`Pyramid Documentation`: http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/testing.html
.. _Splinter: https://splinter.readthedocs.io/en/latest/
.. _`phantom\.js`: http://phantomjs.org/