Rabbil Yasar Sajal
Posted on June 18, 2021
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
- Types Of Testing
- What Should Be Tested
- Our Testing Project
- Structure
- Third Party Packages
- Django Package
- Testing
- 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.
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.
└── app_name └── tests ├── __init__.py ├── test_forms.py ├── test_models.py └── test_views.py
By default django's unittest
module uses its builtin-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: 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: This is a utilitarian tool for isolating part of your code that are not critical to the test you are writing.
model-bakery: 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. Despite the name this module can be used for both unit test and integration test. There are other modules like pytest, 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.
Testing
TestCase
Django provides us with few base classes (SimpleTestCase, LiveServerTestCase, TransactionTestCase, 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.
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. 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 thansetUp()
.
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
Django specific assert methods:
django unittest assert
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:
# 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, GitHub, LinkedIn
Posted on June 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.