下载工作台
Django Web 开发

测试、安全与生产配置

试读上半部分 · 解锁后可读全文

第 11 章 · 测试、安全与生产配置

本章目标:使用 pytest-django 与 Django TestCase 编写模型、API 与视图测试;理解 DEBUG=False 下的行为差异;通过环境变量管理 SECRET_KEY 与数据库凭据;掌握 Gunicorn + Nginx 部署流程与 HTTPS 相关头;为 shop-demo 建立可上线的最小安全配置清单。

学时建议:5~6 小时(含 2 小时部署演练)

前置:本模块 ch01~ch10python-dev ch10 虚拟环境与依赖。运维概念可对照 ops-deploy 模块。


11.1 测试金字塔(Django 视角)

        ┌─────────┐
        │ E2E 少量 │  Selenium / Playwright(选修)
        ├─────────┤
        │ API 集成 │  DRF APIClient、pytest-django
        ├─────────┤
        │ 单元较多 │  Model、Serializer、utils
        └─────────┘
层级工具shop-demo 示例
单元pytestvalidate_price、Serializer 校验
集成TestCase / APIClient商品 CRUD API
E2E浏览器自动化登录后台(毕业项目加分)
本章测试均针对虚构 shop-demo,不依赖任何企业内部 CI 或私有镜像仓库。

11.2 pytest-django 环境搭建

pip install pytest pytest-django pytest-cov

pytest.ini(项目根):

[pytest]
DJANGO_SETTINGS_MODULE = shopdemo.settings
python_files = tests.py test_*.py *_tests.py
addopts = -ra --strict-markers

conftest.py

import pytest


@pytest.fixture
def api_client():
    from rest_framework.test import APIClient
    return APIClient()


@pytest.fixture
def admin_user(db):
    from django.contrib.auth import get_user_model
    User = get_user_model()
    return User.objects.create_superuser(
        username="admin-demo",
        email="admin@user-demo.example.com",
        password="TestPass123!",
    )

运行:

pytest
pytest catalog/tests/test_api.py -v
pytest --cov=catalog --cov-report=term-missing

11.3 模型与 Serializer 测试

catalog/tests/test_models.py

import pytest
from decimal import Decimal
from catalog.models import Product, Category


@pytest.mark.django_db
def test_product_str():
    cat = Category.objects.create(name="图书", slug="books")
    p = Product.objects.create(
        name="Django 实战",
        slug="django-book",
        price=Decimal("88.00"),
        stock=10,
        category=cat,
    )
    assert str(p) == "Django 实战"


@pytest.mark.django_db
def test_product_price_positive():
    from catalog.serializers import ProductWriteSerializer
    data = {"name": "x", "slug": "x", "price": "-1", "stock": 1, "category": 1}
    ser = ProductWriteSerializer(data=data)
    assert not ser.is_valid()
    assert "price" in ser.errors

11.4 API 测试(DRF APIClient)

catalog/tests/test_api.py

import pytest
from decimal import Decimal
from django.urls import reverse
from rest_framework import status
from catalog.models import Product, Category


@pytest.mark.django_db
def test_product_list_anonymous(api_client):
    cat = Category.objects.create(name="图书", slug="books")
    Product.objects.create(
        name="测试书", slug="test-book", price=Decimal("29.9"),
        stock=5, category=cat, is_published=True,
    )
    url = reverse("product-list")
    res = api_client.get(url)
    assert res.status_code == status.HTTP_200_OK
    assert res.data["count"] == 1
    assert res.data["results"][0]["name"] == "测试书"


@pytest.mark.django_db
def test_product_create_requires_auth(api_client):
    url = reverse("product-list")
    res = api_client.post(url, {"name": "x", "slug": "x", "price": "10"}, format="json")
    assert res.status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)


@pytest.mark.django_db
def test_product_create_authenticated(api_client, admin_user):
    cat = Category.objects.create(name="图书", slug="books")
    api_client.force_authenticate(user=admin_user)
    url = reverse("product-list")
    payload = {
        "name": "新品",
        "slug": "new-sku",
        "price": "99.00",
        "stock": 100,
        "category": cat.id,
    }
    res = api_client.post(url, payload, format="json")
    assert res.status_code == status.HTTP_201_CREATED
    assert Product.objects.filter(slug="new-sku").exists()

11.5 Django TestCase 与客户端

from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse


class ProductViewTests(TestCase):
    def setUp(self):
        self.client = Client()
        User = get_user_model()
        self.user = User.objects.create_user(username="op", password="pass12345")

    def test_login_and_create_product_page(self):
        self.client.login(username="op", password="pass12345")
        response = self.client.get(reverse("product_create"))
        self.assertEqual(response.status_code, 200)
pytest-djangoTestCase
函数式、fixture 灵活类式、Django 传统
与 pytest 生态统一django.test 内置
推荐新项目 API 测试模板/表单测试均可

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。