Common Best Practices

  • If you don't know what are the unit test for your function should be, try not to use other testing tools like, Browser or Postman etc. The only tool to test if your code run correctly is unit test.
  • If the test is for Django Model, Form or View etc, use django.test.TestCase.
  • If the test is for Django REST, use rest_framework.test.APITestCase.
  • Test must be isolated, it must not required other test case to pass.
  • Test must not required to run in sequential.
  • Test function name should tell what intended to do and the reason why it failed or passed.
# Yes
def test_create_person_should_fail_when_name_is_already_existed(self):
    ...

# No
def test_api(self):
    ...
  • Show more information when assertion failed, easy for debugging.
# Yes
def test_create_person_should_success(self):
    res = self.client.post(url, data)
    self.assertEqual(res.status, HTTP_201_CREATED, res.json())

# No
def test_create_person_should_success(self):
    res = self.client.post(url, data)
    self.assertEqual(res.status, HTTP_201_CREATED)
  • Use setUp method to create a sharable data in the current test suite.
  • Do not hardcoded url, use reverse instead.
class TestSimple(TestCase):
    def setUp(self):
        self.user = User.objects.get(id=1)
        # Yes
        self.url = reverse('api:person')
        # No
        self.url = '/api/person/'
    
    def test_something_should_be_succeed(self):
        self.url ...
        self.user ...
    
    def test_something_should_be_failed(self):
        self.url ...
        self.user ...
  • Use Django Build-in assertion instead of python assertion, because it provided more information when assertion failed.
class TestSimple(TestCase):
    def test_something_should_be_failed(self):
        # Yes
        self.assertIsNotNone(...)
        self.assertIsNone(...)
        self.assertTrue(...)
        self.assertFalse(...)
        self.assertEqual(...)
        # No
        assert a is not None
        assert b is None
        assert a == True
        assert b == False
        assert a == b
  • You don't have to coverage 100% of unit test, but you must at least sure that your code work as intended.
  • Do not write unit test for third-party packaged or framework you are using, because it should already have its own unit test.

Fixtures

Introduction

You should use fixtures if it's the kind of data that will be reusable. Example:

  • Role
  • Type

Rules

Name of fixture should be noun in plural form.

# Yes  
users.json  
people.json
departments.json

# No
do_something.json  # Not Noun
persons.json  # Incorrect grammar

One JSON fixture file for one model.

# Yes
[
    {"model": "app.Model", "pk": 1, ...},
    {"model": "app.Model", "pk": 2, ...}
]

# No
[
    {"model": "app.Model1", "pk": 1, ...},
    {"model": "app.Model2", "pk": 1, ...}
]

Do not write your own fixtures, always generate from Django Admin command line.

$ python manage.py dumpdata app.Model >> path/app/fixtures/model.json

If you have a set of fixtures that can be reusable, consider creating a reusable constants.

# Yes
## fixture_templates.py
USERS = [
    'users.json',
]

PEOPLE = USERS + [
    'people.json',
]

## tests.py
class UserTest(tests.TestCase):
    fixtures = fixture_templates.PEOPLE

class PersonTest(tests.TestCase):
    fixtures = fixture_templates.PEOPLE

# No
class UserTest(tests.TestCase):
    fixtures = [
        'users.json',
        'people.json',
    ]

class PersonTest(tests.TestCase):
    fixtures = [
        'users.json',
        'people.json',
    ]

Note: The fixture_templates.py file should be in common app directory.

Parallel Testing

Running test in parallel could speed up your test suite. Read More

Sample usage:

python manage.py test --parallel=2
# With docker-compose
docker-compose exec django python manage.py test --parallel=2

If you are using test coverage, make sure to run combine after run the coverage:

$ coverage run
$ coverage combine
$ coverage report -m