Booktype core testing

Setup

Booktype source comes with ready tests. To be able to run tests one must install required development dependencies (if already functional Booktype project does not exists).

$ virtualenv --distribute bkproject
$ source bkproject/bin/activate
$ pip install -r <PATH_TO_BOOKTYPE>/requirements/dev.txt
$ cd <PATH_TO_BOOKTYPE>/tests

Tests

This is for Unit tests and some Integration tests. Works with in memory Sqlite3 database. Does not require any external service to be running.

Discover runner is configured to use ‘test_*.py’ pattern to find available tests. Django settings file used for the tests is settings.py.

$ ./start_tests.sh

Configuration

Using settings.py file in tests/ directory. Definition for URL is in urls.py file.

When to write them

Mainly when writing Unit Tests. Considering it uses Sqlite3 database it can also be used for some Integration tests which are working with database.

Example

Example lib/booktype/apps/core/tests/test_templatetags.py.

import mock

from django.test import TestCase

from ..templatetags import booktype_tags

class UsernameTest(TestCase):
    def test_anonymous(self):
        user = mock.Mock(username = 'Booktype')
        user.is_authenticated.return_value = False

        self.assertEquals(booktype_tags.username(user), "Anonymous")


    def test_no_firstname(self):
        user = mock.Mock(username='booktype', first_name='')
        user.is_authenticated.return_value = True

        self.assertEquals(booktype_tags.username(user), "booktype")

Functional Tests

This is for some Integration tests and all Functional tests . Works with in memory sqlite3 database out of the box but could/should be configured to work with PostgreSQL also. To run these tests you need to have running Redis, RabbitMQ, Celery workers. This also includes Selenium tests.

Discover runner is configured to use ‘functest_*.py’ pattern to find available tests. Django settings file used for the tests is func_settings.py.

$ ./start_functests.sh

Configuration

Using func_settings.py file in tests/ directory. Definition for URL is in urls.py file.

When to write them

For writing Integration tests and Functional tests. Tests are configured to use all external services (Redis, RabitMQ) and can Selenium can be freely used with them.

Write tests which are testing background workers for book conversion, communication with Sputnik, what our Web Server is returning to us, are we generating correct web pages and etc.

Example

Example lib/sputnik/tests/functest_connect.py

import json

from django.test import TestCase
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import User

import sputnik

class ConnectTest(TestCase):
    ERROR_MESSAGE = {'messages': [], 'result': False, 'status': False}
    EMPTY_MESSAGE = {'messages': [], 'result': True, 'status': True}

    def setUp(self):
        self.dispatcher = reverse('sputnik_dispatcher')
        user = User.objects.create_user('booktype', 'booktype@booktype.pro', 'password')

    def test_anon_get_connect(self):
        response = self.client.get(self.dispatcher)

        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.content, json.dumps(self.ERROR_MESSAGE))

    def test_get_connect(self):
        self.client.login(username='booktype', password='password')
        response = self.client.get(self.dispatcher, follow=True)

        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.content, json.dumps(self.ERROR_MESSAGE))

Rules when writing tests

Each test method tests one thing. A test method must be extremely narrow in what it tests. A single test should never assert the behavior of multiple views, models, forms or even multiple methods within a class.

For views when possible use the Request factory. The django.tets.client.RequestFactory provides a wat to generate a request instance that can be used as the first argument to any view. This provides a greater amount of isolation then the standard Django test client, but it does require a little bit of extra work.

from django.test import TestCase
from django.test.client import RequestFactory

class SimpleTest(TestCase):
    def test_details(self):
        factory = RequestFactory()

        request = factory.get('/customer/details')

Don’t write tests that have to be tested.

No Fixtures. Working with fixtures can be problematic when we upgrade our models. New fixture data needs to be created plus all references to it (in the code and in the tests) must be updated. It is recommended to use tools for generating test data like Factory Boy (https://pypi.python.org/pypi/factory_boy/).

Use mock objects. In our Unit Tests we should use as much as possible mock objects (if it is possible). For that we use Python Mocking and Patching Library for Testing (https://pypi.python.org/pypi/mock).

More info