Django: Testing The What, Why and How (Theory).
Tags: django, testing, unittest, python
Published: 2021-06-18 14:17:50

A website cannot be perfect as it is nearly impossible for a website to run perfectly the first time without errors. As the project gets large it becomes hard for all the developers to keep track of all the changes; one single change in an existing component can make the whole project to break. This is why it is very essential to test every single functionality. 


## Table Of Content
1. [Types Of Testing] (#types-of-testing)
2. [What Should Be Tested] (#what-should-be-tested)
3. [Our Testing Project] (#our-testing-project)
4. [Structure] (#structure)
5. [Third Party Packages] (#third-party-packages)
   - [coverage] (#coverage)
6. [Django Package] (#django-package)
   - [client] (#client) 
7. [Testing] (#testing)
   - [TestCase] (#testcase)
   - [How To Run A Test] (#how-to-run-a-test)
8. [Next] (#next) 

## Types Of Testing
Unit and Integration testing are two main types of testing.
- *Unit Tests* are isolated tests responsible for only one functionality. 
- *Integration Tests* are aimed at mimicking the user behavior, usually combined with multiple functions to make sure they behave correctly.

As Unit Tests are small they should be written all the time. These tests are easy to debug. The more you write these, the lesser you will likely to write integration testing. However, sometimes it is essential to run integration testing.

## What Should Be Tested
- If something can break, it should be tested. These includes models, views, forms, templates, validators and so on.
- Each test should be focused on testing one functionality.
- It should run whenever a code is being PUSHed or PULLed from a repo in the staging environment, before PUSHing to production.

That being said if the code in question is a builtin django or python function/library, don't test it. For example datetime or model fields. The only time you test something if the function or code is written by you. 

## Our Testing Project
In this tutorial we will be using a very basic project [book-library] (https://github.com/rabbilyasar/book-library).
To set up the project, first we will need to clone the repo.
```
git clone https://github.com/rabbilyasar/book-library.git
```
Install virtualenv if not installed already
```
pip install virtualenv
```
activate our virtualenv
```
source env/bin/activate
```
install the packages in our *requirements.txt*
```
pip install -r requirements.txt
```

```
asgiref==3.3.4
autopep8==1.5.7
coverage==5.5
Django==3.2.4
mccabe==0.6.1
pycodestyle==2.7.0
pytz==2021.1
sqlparse==0.4.1
toml==0.10.2
```
Migrate our database
```
python manage.py migrate
```

## Structure
When you create a django app it includes a *test.py* file that you can use to write your tests, however, I prefer to create a module called *tests* and have separate files for our models, forms, views.
***Note: If the project gets big it gets hard to maintain in this structure. Another way of doing would be to have a folder for models, views and forms and create file for each view or model.***
We would be following the following structure for our project.

<pre>
└── app_name
    └── tests
        ├── __init__.py
        ├── test_forms.py
        ├── test_models.py
        └── test_views.py
</pre>

By default django's `unittest` module uses its [builtin-test-discovery] (https://docs.python.org/3/library/unittest.html#unittest-test-discovery). It will discover tests under the current directory with filename pattern __test*.py__. For our current structure to work we will have to delete our existing *tests.py* file which has been provided by our django app.

## Third Party Packages
- [coverage](https://coverage.readthedocs.io/en/latest/): It can be used to have  a rough overview of a project's total test coverage. It is a remarkable tool for any beginner as it can give you suggestion on what needs to be tested. 

- [mock] (https://pypi.org/project/mock/): This is a utilitarian tool for isolating part of your code that are not critical to the test you are writing.

- [model-bakery] (https://model-bakery.readthedocs.io/en/latest/): This is a fine tool for creating fixtures for testing. We will go through some usage and examples in the next blog.

### Coverage
Let's a take a look at coverage and see how we can use it.
To run coverage inside a project:
Install coverage.
```
pip install coverage
```
After every time you add some code to your application run this.

```
coverage run --omit='*/venv/*' manage.py test
```
If you want to see more details you can add -v flag. In this case it is using verbosity level 2.
```
coverage run manage.py test -v 2
```
After running it once we can can get a coverage report using:
```
coverage report
```
We can also generate an HTML report in a new folder called *htmlcov*
```
coverage html
```

## Django Package
Django's default framework for testing is Python's standard library module [unittest] (https://docs.python.org/3/library/unittest.html#module-unittest). Despite the name this module can be used for both unit test and integration test. There are other modules like [pytest] (https://docs.pytest.org/en/6.2.x/), which has it's own benefits and drawbacks on which we will focus later.

### Client
Django's test client is an amazing tool which can be used for integration testing. It can simulate `GET` and `POST` requests on a `URL` and one can observe the response. [Test Client] (https://docs.djangoproject.com/en/3.0/topics/testing/tools/#the-test-client).

## Testing
### TestCase
Django provides us with few base classes ([SimpleTestCase](https://docs.djangoproject.com/en/3.1/topics/testing/tools/#simpletestcase), [LiveServerTestCase] (https://docs.djangoproject.com/en/3.1/topics/testing/tools/#liveservertestcase), [TransactionTestCase](https://docs.djangoproject.com/en/3.1/topics/testing/tools/#transactiontestcase), [TestCase] (https://docs.djangoproject.com/en/3.1/topics/testing/tools/#testcase)). You can derive your test class from any of the base classes and have your own method which will test for a specific functionality.

```python
class YourTestClass(TestCase):
    def setUp(self):
        # Setup run before every test method.
        pass

    def tearDown(self):
        # Clean up run after every test method.
        pass

    def test_something_that_will_pass(self):
        self.assertFalse(False)

    def test_something_that_will_fail(self):
        self.assertTrue(False)
```

The best base class for most tests is [django.test.TestCase](https://docs.djangoproject.com/en/3.1/topics/testing/tools/#testcase). This creates a clean database before its tests are run, and runs every test function in its own transaction. The test `client` can be derived from this. *TestCase* provides us with an additional method `setUpTestData` along with `setUp` for setting up our data.

- *`setUp()`*: It is called before every test method. This is useful when creating object that will need modifying before each test method.
- *`setUpTestData()`*: This is called once before running the whole test. You use this one when you know the data you will create in this method will be the same for every test method. As this is run once for every test class, it is faster than `setUp()`.

The `tearDown()` method is not very useful when it comes to test that involves database. Moreover `TestCase` does a full database flush at the start of a new test, so  it will take care of the teardown for you.

We have some test methods provided to us by ***unittest***, which can be used to test the condition of our code. Some standard assertions are:
- `AssertTrue`: Checks if the condition is true.
- `AsserFalse`: Checks if the condition is false.
- `AssertEqual`: Checks if the condition is equal.

These are some of the python specific assert methods:
[python unittest assert] (https://docs.python.org/3/library/unittest.html#assert-methods)

Django specific assert methods:
[django unittest assert] (https://docs.djangoproject.com/en/3.2/topics/testing/tools/#assertions)

### How To Run A Test
To run all the test in the project use the command:
```
python manage.py test
```
This will discover all files with the pattern __test*.py__ under the directory and run all tests defined.
If we want to show more test information we can change the verbosity like below:
```
python manage.py test --verbosity 2
```
Note: Allowed verbosity levels are 0,1,2 and 3, the default being 1.
To run  a specific test:
```python
# Run the specified module
python3 manage.py test app.tests

# Run the specified module
python3 manage.py test app.tests.test_models

# Run the specified class
python3 manage.py test app.tests.test_models.YourTestClass

# Run the specified method
python3 manage.py test app.tests.test_models.YourTestClass.your_test_method
```
## Next
In the next part of our series we will get our hands dirty and will see how we can implement unit testing to our book-library.

If you like what I write and want to support me and my work, please follow me on [twitter] (https://twitter.com/rabbilyasar), [GitHub] (https://github.com/rabbilyasar), [LinkedIn](https://www.linkedin.com/in/rabbilyasar/)