返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Django

Django's Unit Tests and Integration Tests

Testing was indeed often overlooked in the past, especially for rapid development frameworks like Python Web.

曦远 (DealiAxy)约 19 分钟
目录8 个章节

Preface

It's been a while since I worked with Django, and recently maintaining a project I built with it gave me a strangely nostalgic feeling 😂

Testing, in the past, was indeed a topic often overlooked—especially for rapid-development frameworks like Python Web, where agility was prioritized. The approach was "build fast, launch fast" rather than "take time to do it right." This rough development style often made the project hard to maintain later on, which is how Python got the reputation of being easy to develop with but hard to maintain.

Python: I'm not taking the blame for that 😒

Getting back on topic, the testing in this case involves two parts: writing unit tests and integration tests within the Django project to ensure its functionality works correctly. Then, I also developed an independent automated testing tool that can test based on OpenAPI documentation and output a test report afterward. The report includes details like whether each API endpoint passed the test and the response time, etc.

I built this tool using Go, mainly because Go makes cross-compilation straightforward. The generated executable can be directly uploaded to a server and executed, which is incredibly convenient.

I’ll introduce this automated testing tool in the next post.

Testing in Django

I must say, Django's documentation is really excellent 👍

After reading the documentation for a while, I started writing tests. The full Django suite is truly comfortable, and the development experience is incredibly smooth.

When using startapp to create an app, each app directory contains a tests.py file. We can write our test code in this file.

If there are many test cases, you can split them. Refer to how views.py is split—convert tests.py into a package. Create a tests directory, place individual test files inside it, and import them in __init__.py.

Testing is divided into unit testing and integration testing. Writing unit tests in Django is much more comfortable than in AspNetCore. You don't need to worry about dependency injection—Django handles it all for you.

During testing, Django automatically creates a test database, so you don't have to deal with environment isolation yourself.

Regarding the differences between these two types of testing, I covered this in a previous article, so I won't copy and paste it here.

Asp-Net-Core Learning Notes: Unit Testing and Integration Testing

Examples

This time, we'll use two apps as examples

  • config - unit tests (单元测试)
  • dashboard - integration tests (集成测试)

In Django, both types of tests come with minimal mental overhead and require no additional setup. You can simply write them directly in tests.py.

Unit Testing

Using the config app as an example for unit testing.

I have encapsulated a ConfigService with the following code:

from datetime import date
from .models import CommonConfig


class ConfigService(object):
    def __init__(self):
        ...

    @staticmethod
    def get_config(key: str) -> str:
        queryset = CommonConfig.objects.filter(key=key)
        if queryset.exists():
            return queryset.first().value
        return ''

    @property
    def today(self):
        return date.today()

    @property
    def start_year(self):
        value = self.get_config('start_year')
        return str(self.today.year) if len(value) == 0 else value

    @property
    def start_month(self):
        value = self.get_config('start_month')
        return str(self.today.month) if len(value) == 0 else value

    @property
    def end_year(self):
        value = self.get_config('end_year')
        return str(self.today.year) if len(value) == 0 else value

    @property
    def end_month(self):
        value = self.get_config('end_month')
        return str(self.today.month) if len(value) == 0 else value

Edit apps/config/tests.py

from django.test import TestCase
from apps.config.models import CommonConfig
from apps.config.services import ConfigService


class CommonConfigTestCase(TestCase):
    def setUp(self):
      CommonConfig.objects.create(key='start_year', value='2023')
      CommonConfig.objects.create(key='end_year', value='2024')
      CommonConfig.objects.create(key='start_month', value='1')
      CommonConfig.objects.create(key='end_month', value='10')

    def test_common_config(self):
      cfg = ConfigService()
      self.assertEqual(cfg.start_year, '2023')
      self.assertEqual(cfg.end_year, '2024')
      self.assertEqual(cfg.start_month, '1')
      self.assertEqual(cfg.end_month, '10')

Integration Testing

Integration testing involves simulating HTTP requests to access endpoints and check their functionality.

Django's TestCase class comes with a built-in client attribute, making it convenient to send requests to endpoints.

Generally, endpoints require authentication to use, and client thoughtfully integrates Django's authentication and authorization system—you can just use login directly.

Note that a temporary database is automatically created during testing, so you need to add users first.

After that, you can use methods like self.client.get and self.client.post to test the endpoints.

from django.test import TestCase
from django.shortcuts import reverse
from django.contrib.auth.models import User
from rest_framework import status


class DashboardTests(TestCase):
    def setUp(self):
        User.objects.create_user(username='user', password='pwd')
        self.client.login(username="user", password="pwd")

    def test_overview(self):
        resp = self.client.get(reverse('dashboard:overview'), {'grant_year': 2023, 'grant_month': 2})
        self.assertEqual(resp.status_code, status.HTTP_200_OK)
        self.assertEqual(resp.json()['code'], status.HTTP_200_OK)

    def test_monthly_data(self):
        resp = self.client.get(reverse('dashboard:monthly_data'), {'grant_year': 2023, 'grant_month': 2})
        self.assertEqual(resp.status_code, status.HTTP_200_OK)
        self.assertEqual(resp.json()['code'], status.HTTP_200_OK)

    def test_county_data(self):
        resp = self.client.get(reverse('dashboard:county_data'), {'grant_year': 2023, 'grant_month': 2})
        self.assertEqual(resp.status_code, status.HTTP_200_OK)
        self.assertEqual(resp.json()['code'], status.HTTP_200_OK)

Running Tests

After writing tests

Use the command to run the tests

python manage.py test

For other parameters, please refer to the official documentation.

However, when running tests, it’s not as satisfying as in AspNetCore; there’s no excitement of seeing each endpoint light up one after another.

In Django, you can only see the test results—how many tests passed—and if there are errors, you’ll see the Traceback information.

$ python .\manage.py test 
Found 5 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.....
----------------------------------------------------------------------
Ran 5 tests in 0.831s

OK
Destroying test database for alias 'default'...

Summary

Django still feels familiar, and it just works.

References

I didn't realize Django had reached version 5.0 before I knew it…

_EOF_

Django's Unit Tests and Integration Tests

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

Django项目笔记:sessions处理以及复杂对象序列化

前言:一点题外话 我发现,不更新博客的时候,不是非常忙,就是效率非常低。最近没怎么更新博客,原因是第二种= =。惭愧惭愧。 今天效率出奇的高,一天时间把PassNote后端的接口全部写完了,Django很好用,不过实际项目中还是有些框架不能实现的功能需要解决的,我比较喜欢造轮子,不过为了保证效率,还是尽量用现成的工具,减少不必要的折腾。 关于PassNote 之前应该在博客有说过,这是一个密码管理

2022-04-02 · Django · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

Django's Unit Tests and Integration Tests · StarBlog