By default, Django hides errors like non-existent template variables in templates. One way to un-hide them is to install the django-fastdev module.
After installing fastdev, you'll run into errors on startup. Try firing up runserver now that new fatal errors can interrupt app startup.
Next, you'll want to install the coverage python plugin. This allows you to assess test coverage across any Python project. The coverage plugin will assess coverage for most statements, but it doesn't know how to check Django templates. For that, you'll need to get an additional module, django-coverage-plugin. To get it working in VScode, I needed to add an environment variable to my .zshrc file. This tells the plugin where your django settings.py file is located.
export DJANGO_SETTINGS_MODULE=django_project.settings
The coverage module works hand-in-hand with unittest (probably pytest and nose too). These are the three most useful CLI commands I run. Check the docs for more info.
coverage run -m unittest discover
coverage report -m --omit 'tests/*' --skip-empty --skip-covered
coverage html --skip-empty --omit tests/tests_isolated -d blog/templates/htmlcov
The first command runs unittest and logs results to a .coverage file used to generate subsequent reports.
The second command generates a report right in the terminal with code coverage. I've added additional parameters.
- omit 'tests/* ...I don't need coverage for my test files. I guess that begs a philosophical question...should unit tests be unit tested? Oh boy...
- skip-empty ...Skips any file that has nothing in it. Otherwise, there will be __init__.py files in the report. Gross.
- skip-covered ...It feels good to have 100% coverage, but it's not very useful. This removes files from the report if they already have 100% code coverage.
The final command, coverage html, generates an html report to send to your colleagues!
Code Coverage for templates
Now that we have a handle on where coverage is lacking, we'll want to test those templates! We don't test templates directly. We test them along with our views. In order to get coverage, write your view tests so that each statement in a template evaluates.
My test_views.py file, contains a function called test_category_view( )
. At first, I was missing coverage in my templates because it wasn't evaluating all the pagination logic. To get more coverage, I needed to make sure there were enough posts to fill several pages besides fetching those pages in order to make sure I hit all the pagination logic contained in my template.
# Paginated list appears when there are many posts
create_several_posts(self.category1.name, self.super_user, 20)
response = self.client.get(self.category_url)
self.assertTrue(response.context['is_paginated'])
self.assertEqual(response.context['posts'].count(), 5) # 5 per page
# Paginated list works when user has moved forward at least one page
response = self.client.get(self.category_url, {'page': 2})
self.assertTrue(response.context['page_obj'].has_previous())
The create_several_posts
function ensures 20 posts are created before the test continues. You'll have to keep playing with your test_views file to get more and more template coverage. Eventually, coverage will look like this:
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------------------------------
django_project/blog/admin.py 11 0 100%
django_project/blog/apps.py 3 0 100%
django_project/blog/forms.py 13 0 100%
django_project/blog/models.py 64 0 100%
django_project/blog/templates/blog/add_comment.html 12 0 100%
django_project/blog/templates/blog/add_post.html 16 0 100%
django_project/blog/templates/blog/categories.html 8 0 100%
django_project/blog/templates/blog/edit_post.html 12 0 100%
django_project/blog/templates/blog/home.html 14 0 100%
django_project/blog/templates/blog/parts/about_me.html 28 0 100%
django_project/blog/templates/blog/parts/base.html 70 0 100%
django_project/blog/templates/blog/parts/footer.html 18 0 100%
django_project/blog/templates/blog/parts/header.html 61 0 100%
django_project/blog/templates/blog/parts/kofi_donation.html 1 0 100%
django_project/blog/templates/blog/parts/mailchimp_embed.html 37 0 100%
django_project/blog/templates/blog/parts/pagination.html 20 0 100%
django_project/blog/templates/blog/parts/posts.html 16 0 100%
django_project/blog/templates/blog/parts/sidebar.html 13 0 100%
django_project/blog/templates/blog/pgp-key.txt 98 0 100%
django_project/blog/templates/blog/post_confirm_delete.html 15 0 100%
django_project/blog/templates/blog/post_detail.html 63 0 100%
django_project/blog/templates/blog/roadmap.html 34 0 100%
django_project/blog/templates/blog/search_posts.html 15 0 100%
django_project/blog/templates/blog/security.txt 5 0 100%
django_project/blog/templates/blog/user_posts.html 3 0 100%
django_project/blog/templates/blog/works_cited.html 11 0 100%
django_project/blog/urls.py 7 0 100%
django_project/blog/utils.py 19 0 100%
django_project/blog/views.py 171 0 100%
django_project/django_project/settings.py 65 0 100%
django_project/django_project/sitemaps.py 17 0 100%
django_project/django_project/urls.py 12 0 100%
django_project/users/admin.py 3 0 100%
django_project/users/apps.py 5 0 100%
django_project/users/forms.py 23 0 100%
django_project/users/models.py 18 0 100%
django_project/users/signals.py 11 0 100%
django_project/users/templates/users/login.html 22 0 100%
django_project/users/templates/users/logout.html 7 0 100%
django_project/users/templates/users/password_reset.html 13 0 100%
django_project/users/templates/users/password_reset_complete.html 5 0 100%
django_project/users/templates/users/password_reset_done.html 4 0 100%
django_project/users/templates/users/profile.html 21 0 100%
django_project/users/templates/users/register.html 20 0 100%
django_project/users/views.py 67 0 100%
-------------------------------------------------------------------------------------------------
TOTAL 1171 0 100%
Good luck testing your templates!
Comments
- No comments yet.
John Solly
Hi, I'm John, a Software Engineer with a decade of experience building, deploying, and maintaining cloud-native geospatial solutions. I currently serve as a senior software engineer at HazardHub (A Guidewire Offering), where I work on a variety of infrastructure and application development projects.
Throughout my career, I've built applications on platforms like Esri and Mapbox while also leveraging open-source GIS technologies such as OpenLayers, GeoServer, and GDAL. This blog is where I share useful articles with the GeoDev community. Check out my portfolio to see my latest work!
0