第15章:测试
15.1 单元测试
TestCase类
Django提供了强大的测试框架,基于Python的unittest模块:
python
# tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from django.urls import reverse
from .models import Article, Category, Tag
class ArticleModelTest(TestCase):
"""文章模型测试"""
def setUp(self):
"""测试前的准备工作"""
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
self.category = Category.objects.create(
name='测试分类',
slug='test-category'
)
self.tag = Tag.objects.create(
name='测试标签',
slug='test-tag'
)
def test_article_creation(self):
"""测试文章创建"""
article = Article.objects.create(
title='测试文章',
slug='test-article',
author=self.user,
category=self.category,
content='这是测试文章的内容',
excerpt='这是测试文章的摘要',
status='published'
)
article.tags.add(self.tag)
# 验证文章是否正确创建
self.assertEqual(article.title, '测试文章')
self.assertEqual(article.author, self.user)
self.assertEqual(article.category, self.category)
self.assertIn(self.tag, article.tags.all())
self.assertTrue(article.is_published)
def test_article_string_representation(self):
"""测试文章字符串表示"""
article = Article.objects.create(
title='测试文章标题',
slug='test-article',
author=self.user,
content='内容'
)
self.assertEqual(str(article), '测试文章标题')
def test_article_get_absolute_url(self):
"""测试文章绝对URL"""
article = Article.objects.create(
title='测试文章',
slug='test-article',
author=self.user,
content='内容'
)
expected_url = reverse('blog:article_detail', kwargs={'slug': 'test-article'})
self.assertEqual(article.get_absolute_url(), expected_url)
class CategoryModelTest(TestCase):
"""分类模型测试"""
def test_category_creation(self):
"""测试分类创建"""
category = Category.objects.create(
name='技术',
description='技术相关文章'
)
self.assertEqual(category.name, '技术')
self.assertEqual(category.slug, 'ji-zhu') # 自动创建的slug
self.assertEqual(str(category), '技术')
def test_category_slug_generation(self):
"""测试分类slug自动生成"""
category = Category.objects.create(name='Python编程')
self.assertEqual(category.slug, 'pythonbian-cheng')
class ArticleViewTest(TestCase):
"""文章视图测试"""
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.category = Category.objects.create(name='测试分类')
self.article = Article.objects.create(
title='测试文章',
author=self.user,
category=self.category,
content='测试内容',
status='published'
)
def test_article_list_view(self):
"""测试文章列表视图"""
response = self.client.get(reverse('blog:article_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
self.assertTemplateUsed(response, 'blog/article_list.html')
def test_article_detail_view(self):
"""测试文章详情视图"""
response = self.client.get(
reverse('blog:article_detail', kwargs={'slug': self.article.slug})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
self.assertContains(response, '测试内容')
self.assertTemplateUsed(response, 'blog/article_detail.html')
def test_article_detail_view_404(self):
"""测试不存在的文章详情视图"""
response = self.client.get(
reverse('blog:article_detail', kwargs={'slug': 'non-existent'})
)
self.assertEqual(response.status_code, 404)
# 使用工厂模式简化测试数据创建
# factories.py
import factory
from django.contrib.auth.models import User
from .models import Article, Category, Tag
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
password = factory.PostGenerationMethodCall('set_password', 'defaultpass123')
class CategoryFactory(factory.django.DjangoModelFactory):
class Meta:
model = Category
name = factory.Sequence(lambda n: f"分类{n}")
description = factory.Faker('text', max_nb_chars=200)
class TagFactory(factory.django.DjangoModelFactory):
class Meta:
model = Tag
name = factory.Sequence(lambda n: f"标签{n}")
class ArticleFactory(factory.django.DjangoModelFactory):
class Meta:
model = Article
title = factory.Faker('sentence', nb_words=6)
content = factory.Faker('text', max_nb_chars=1000)
excerpt = factory.Faker('text', max_nb_chars=200)
author = factory.SubFactory(UserFactory)
category = factory.SubFactory(CategoryFactory)
status = 'published'
# 使用工厂的测试示例
class ArticleFactoryTest(TestCase):
"""使用工厂的测试示例"""
def test_create_multiple_articles(self):
"""测试创建多个文章"""
# 创建5篇文章
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertEqual(Article.objects.count(), 5)
def test_create_article_with_tags(self):
"""测试创建带标签的文章"""
tags = TagFactory.create_batch(3)
article = ArticleFactory(tags=tags)
self.assertEqual(article.tags.count(), 3)数据库测试
数据库相关的测试:
python
# test_models.py
from django.test import TestCase
from django.db import IntegrityError
from django.core.exceptions import ValidationError
from .models import Article, Category, Tag
class ModelDatabaseTest(TestCase):
"""模型数据库测试"""
def setUp(self):
self.user = UserFactory()
self.category = CategoryFactory()
def test_unique_slug_constraint(self):
"""测试唯一slug约束"""
ArticleFactory(slug='unique-slug')
with self.assertRaises(IntegrityError):
ArticleFactory(slug='unique-slug')
def test_category_name_unique(self):
"""测试分类名称唯一性"""
CategoryFactory(name='唯一分类')
with self.assertRaises(IntegrityError):
CategoryFactory(name='唯一分类')
def test_article_validation(self):
"""测试文章验证"""
article = Article(
title='', # 空标题应该引发验证错误
author=self.user,
content='内容'
)
with self.assertRaises(ValidationError):
article.full_clean()
def test_article_save_method(self):
"""测试文章保存方法"""
article = ArticleFactory(
title='测试文章',
excerpt='' # 空摘要应该自动生成
)
self.assertTrue(len(article.excerpt) > 0)
def test_foreign_key_cascade(self):
"""测试外键级联删除"""
article = ArticleFactory(category=self.category)
category_id = self.category.id
# 删除分类
self.category.delete()
# 文章应该仍然存在,但分类为空
article.refresh_from_db()
self.assertIsNone(article.category)
self.assertTrue(Article.objects.filter(id=article.id).exists())表单测试
表单测试确保表单验证和处理逻辑正确:
python
# test_forms.py
from django.test import TestCase
from .forms import ArticleForm, CommentForm
from .models import Article, Category, Tag
class FormTest(TestCase):
"""表单测试"""
def setUp(self):
self.user = UserFactory()
self.category = CategoryFactory()
self.tag = TagFactory()
def test_article_form_valid_data(self):
"""测试文章表单有效数据"""
form_data = {
'title': '测试文章',
'category': self.category.id,
'content': '文章内容',
'excerpt': '文章摘要',
'status': 'published'
}
form = ArticleForm(data=form_data)
self.assertTrue(form.is_valid())
def test_article_form_invalid_data(self):
"""测试文章表单无效数据"""
form_data = {
'title': '', # 标题为空
'content': '', # 内容为空
}
form = ArticleForm(data=form_data)
self.assertFalse(form.is_valid())
self.assertIn('title', form.errors)
self.assertIn('content', form.errors)
def test_article_form_save(self):
"""测试文章表单保存"""
form_data = {
'title': '测试文章',
'category': self.category.id,
'content': '文章内容',
'excerpt': '文章摘要',
'status': 'published'
}
form = ArticleForm(data=form_data)
self.assertTrue(form.is_valid())
article = form.save(commit=False)
article.author = self.user
article.save()
self.assertEqual(article.title, '测试文章')
self.assertEqual(article.author, self.user)
def test_comment_form_valid_data(self):
"""测试评论表单有效数据"""
form_data = {
'content': '这是一条评论'
}
form = CommentForm(data=form_data)
self.assertTrue(form.is_valid())
def test_comment_form_empty_content(self):
"""测试评论表单空内容"""
form_data = {
'content': ''
}
form = CommentForm(data=form_data)
self.assertFalse(form.is_valid())
self.assertIn('content', form.errors)视图测试
视图测试确保HTTP请求处理正确:
python
# test_views.py
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from .models import Article, Category
class ViewTest(TestCase):
"""视图测试"""
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.staff_user = User.objects.create_user(
username='staffuser',
password='staffpass123',
is_staff=True
)
self.category = Category.objects.create(name='测试分类')
self.article = Article.objects.create(
title='测试文章',
author=self.user,
category=self.category,
content='测试内容',
status='published'
)
def test_home_page_status_code(self):
"""测试首页状态码"""
response = self.client.get(reverse('blog:home'))
self.assertEqual(response.status_code, 200)
def test_home_page_contains_correct_html(self):
"""测试首页包含正确HTML"""
response = self.client.get(reverse('blog:home'))
self.assertContains(response, '测试文章')
self.assertContains(response, '<title>')
def test_home_page_uses_correct_template(self):
"""测试首页使用正确模板"""
response = self.client.get(reverse('blog:home'))
self.assertTemplateUsed(response, 'blog/article_list.html')
def test_article_detail_view_with_existing_article(self):
"""测试存在的文章详情视图"""
response = self.client.get(
reverse('blog:article_detail', kwargs={'slug': self.article.slug})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
def test_article_detail_view_with_nonexistent_article(self):
"""测试不存在的文章详情视图"""
response = self.client.get(
reverse('blog:article_detail', kwargs={'slug': 'nonexistent'})
)
self.assertEqual(response.status_code, 404)
def test_create_article_view_requires_login(self):
"""测试创建文章视图需要登录"""
response = self.client.get(reverse('blog:article_create'))
# 应该重定向到登录页面
self.assertEqual(response.status_code, 302)
def test_create_article_view_with_authenticated_user(self):
"""测试已认证用户创建文章视图"""
self.client.login(username='testuser', password='testpass123')
response = self.client.get(reverse('blog:article_create'))
self.assertEqual(response.status_code, 200)
def test_search_view(self):
"""测试搜索视图"""
response = self.client.get(reverse('blog:search'), {'q': '测试'})
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
def test_category_detail_view(self):
"""测试分类详情视图"""
response = self.client.get(
reverse('blog:category_detail', kwargs={'slug': self.category.slug})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')通过这些单元测试,可以确保应用程序的各个组件按预期工作,并在代码更改时快速发现问题。
小结
Django单元测试的核心要点:
- ✅ 使用TestCase类进行模型、视图和表单测试
- ✅ 实施数据库测试验证数据完整性和约束
- ✅ 编写表单测试确保验证逻辑正确
- ✅ 执行视图测试验证HTTP请求处理
- ✅ 使用工厂模式简化测试数据创建
完善的测试体系是保证代码质量和应用稳定性的关键。
下一篇
我们将学习集成测试。