You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Django/doc/blog/tests.py

346 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 导入Python内置os模块用于文件路径操作如图片文件的保存与删除
import os
# 导入Django项目配置模块用于获取项目设置如分页数量、文件上传路径
from django.conf import settings
# 导入Django文件上传相关类用于模拟文件上传请求如图片上传测试
from django.core.files.uploadedfile import SimpleUploadedFile
# 导入Django管理命令调用函数用于在测试中执行自定义管理命令如重建搜索索引
from django.core.management import call_command
# 导入Django分页类用于测试分页功能
from django.core.paginator import Paginator
# 导入Django静态文件模板标签用于生成静态文件URL如测试用户头像
from django.templatetags.static import static
# 导入Django测试核心模块
# 1. Client模拟HTTP客户端用于发送GET/POST请求并获取响应
# 2. RequestFactory生成请求对象用于测试视图函数/模板标签
# 3. TestCaseDjango测试基类提供测试环境初始化、断言方法等
from django.test import Client, RequestFactory, TestCase
# 导入Django URL反向解析模块用于生成测试用的URL避免硬编码
from django.urls import reverse
# 导入Django时区工具用于处理时间字段如模型创建时间
from django.utils import timezone
# 从accounts应用导入用户模型BlogUser自定义用户模型替代Django默认用户模型
from accounts.models import BlogUser
# 从当前应用blog导入搜索表单BlogSearchForm用于测试表单功能
from blog.forms import BlogSearchForm
# 从当前应用导入核心模型:文章、分类、标签、侧边栏、友情链接
from blog.models import Article, Category, Tag, SideBar, Links
# 从当前应用导入自定义模板标签函数,用于测试模板标签的逻辑正确性
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
# 从自定义工具模块导入工具函数:
# 1. get_current_site获取当前站点信息用于生成完整URL
# 2. get_sha256生成SHA256加密字符串用于测试文件上传签名验证
from djangoblog.utils import get_current_site, get_sha256
# 从oauth应用导入第三方登录相关模型OAuth用户、OAuth配置用于测试第三方登录数据
from oauth.models import OAuthUser, OAuthConfig
# 测试类创建标识注释Django测试框架约定测试类需继承TestCase
# Create your tests here.
# 定义文章相关测试类ArticleTest继承自Django的TestCase测试基类
# 作用集中测试博客核心功能包括模型操作、URL访问、搜索、分页、文件上传等
class ArticleTest(TestCase):
# 测试初始化方法:在每个测试方法执行前自动调用,用于准备测试环境
def setUp(self):
# 初始化HTTP客户端模拟用户发送请求
self.client = Client()
# 初始化请求工厂(用于生成原始请求对象,测试视图/模板标签时使用)
self.factory = RequestFactory()
# 核心测试方法测试文章相关完整流程模型创建、关联、URL访问、搜索等
def test_validate_article(self):
# 获取当前站点域名用于验证完整URL生成
site = get_current_site().domain
# 创建/获取测试用户邮箱、用户名固定若已存在则直接获取get_or_create返回元组取第0个元素
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
username="liangliangyy")[0]
# 设置用户密码set_password会自动加密避免明文存储
user.set_password("liangliangyy")
# 设置用户为 staff后台管理权限和 superuser超级管理员权限
user.is_staff = True
user.is_superuser = True
# 保存用户信息到数据库
user.save()
# 模拟访问用户个人主页验证响应状态码为200正常访问
response = self.client.get(user.get_absolute_url())
self.assertEqual(response.status_code, 200)
# 模拟访问后台邮件发送日志页面测试后台URL可达性
response = self.client.get('/admin/servermanager/emailsendlog/')
# 模拟访问后台操作日志列表页面测试后台URL可达性
response = self.client.get('admin/admin/logentry/')
# 创建测试侧边栏数据
s = SideBar()
s.sequence = 1 # 排序序号(控制显示顺序)
s.name = 'test' # 侧边栏标题
s.content = 'test content' # 侧边栏内容HTML文本
s.is_enable = True # 启用侧边栏
s.save() # 保存到数据库
# 创建测试分类数据
category = Category()
category.name = "category" # 分类名称
category.creation_time = timezone.now() # 创建时间
category.last_mod_time = timezone.now() # 最后修改时间
category.save() # 保存到数据库
# 创建测试标签数据
tag = Tag()
tag.name = "nicetag" # 标签名称
tag.save() # 保存到数据库
# 创建测试文章数据
article = Article()
article.title = "nicetitle" # 文章标题
article.body = "nicecontent" # 文章内容
article.author = user # 关联作者(测试用户)
article.category = category # 关联分类
article.type = 'a' # 文章类型:普通文章('a'=Article
article.status = 'p' # 文章状态:已发布('p'=Published
article.save() # 保存到数据库
# 验证文章初始标签数量为0未添加标签
self.assertEqual(0, article.tags.count())
# 为文章添加标签(多对多关联)
article.tags.add(tag)
article.save() # 再次保存,更新关联关系
# 验证文章标签数量为1添加成功
self.assertEqual(1, article.tags.count())
# 批量创建20篇测试文章用于测试分页功能
for i in range(20):
article = Article()
article.title = "nicetitle" + str(i) # 标题带序号,避免重复
article.body = "nicetitle" + str(i) # 内容与标题一致
article.author = user # 关联同一测试用户
article.category = category # 关联同一分类
article.type = 'a' # 普通文章类型
article.status = 'p' # 已发布状态
article.save() # 保存文章
article.tags.add(tag) # 关联同一标签
article.save() # 更新关联关系
# 导入Elasticsearch启用标识判断是否启用搜索引擎
from blog.documents import ELASTICSEARCH_ENABLED
# 若启用Elasticsearch测试搜索功能
if ELASTICSEARCH_ENABLED:
call_command("build_index") # 执行自定义管理命令,重建搜索索引
# 模拟发送搜索请求,关键词为'nicetitle'
response = self.client.get('/search', {'q': 'nicetitle'})
self.assertEqual(response.status_code, 200) # 验证搜索页面正常响应
# 模拟访问最后一篇测试文章的详情页验证响应状态码为200
response = self.client.get(article.get_absolute_url())
self.assertEqual(response.status_code, 200)
# 导入爬虫通知工具类,测试文章发布后通知搜索引擎(如百度)
from djangoblog.spider_notify import SpiderNotify
SpiderNotify.notify(article.get_absolute_url()) # 通知搜索引擎该文章URL
# 模拟访问标签详情页验证响应状态码为200
response = self.client.get(tag.get_absolute_url())
self.assertEqual(response.status_code, 200)
# 模拟访问分类详情页验证响应状态码为200
response = self.client.get(category.get_absolute_url())
self.assertEqual(response.status_code, 200)
# 模拟搜索不存在的关键词'django'验证搜索页面正常响应状态码200
response = self.client.get('/search', {'q': 'django'})
self.assertEqual(response.status_code, 200)
# 测试自定义模板标签load_articletags验证返回结果不为空
s = load_articletags(article)
self.assertIsNotNone(s)
# 模拟用户登录(使用测试用户的用户名和密码)
self.client.login(username='liangliangyy', password='liangliangyy')
# 模拟访问文章归档页面验证响应状态码为200
response = self.client.get(reverse('blog:archives'))
self.assertEqual(response.status_code, 200)
# 测试所有文章的分页功能使用项目配置的分页数量settings.PAGINATE_BY
p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
self.check_pagination(p, '', '') # 调用自定义分页测试方法
# 测试标签归档的分页功能:筛选指定标签的文章
p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
self.check_pagination(p, '分类标签归档', tag.slug) # 传递分页类型和标签slug
# 测试作者归档的分页功能:筛选指定作者的文章
p = Paginator(
Article.objects.filter(
author__username='liangliangyy'), settings.PAGINATE_BY)
self.check_pagination(p, '作者文章归档', 'liangliangyy') # 传递分页类型和作者名
# 测试分类归档的分页功能:筛选指定分类的文章
p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
self.check_pagination(p, '分类目录归档', category.slug) # 传递分页类型和分类slug
# 测试搜索表单初始化表单并调用search方法验证表单逻辑无报错
f = BlogSearchForm()
f.search()
# 测试百度爬虫通知功能通知单篇文章的完整URL
from djangoblog.spider_notify import SpiderNotify
SpiderNotify.baidu_notify([article.get_full_url()])
# 测试头像相关模板标签验证gravatar_url和gravatar函数正常返回结果
from blog.templatetags.blog_tags import gravatar_url, gravatar
u = gravatar_url('liangliangyy@gmail.com') # 生成Gravatar头像URL
u = gravatar('liangliangyy@gmail.com') # 生成Gravatar头像HTML标签
# 创建测试友情链接数据
link = Links(
sequence=1, # 排序序号
name="lylinux", # 链接名称
link='https://wwww.lylinux.net') # 链接URL
link.save() # 保存到数据库
# 模拟访问友情链接页面验证响应状态码为200
response = self.client.get('/links.html')
self.assertEqual(response.status_code, 200)
# 模拟访问RSS订阅 feed 页面验证响应状态码为200
response = self.client.get('/feed/')
self.assertEqual(response.status_code, 200)
# 模拟访问站点地图页面验证响应状态码为200
response = self.client.get('/sitemap.xml')
self.assertEqual(response.status_code, 200)
# 模拟访问后台文章删除页面(测试后台操作可达性)
self.client.get("/admin/blog/article/1/delete/")
# 模拟访问后台邮件发送日志页面(重复访问,验证稳定性)
self.client.get('/admin/servermanager/emailsendlog/')
# 模拟访问后台操作日志列表页面(重复访问,验证稳定性)
self.client.get('/admin/admin/logentry/')
# 模拟访问后台操作日志编辑页面(测试后台详情页可达性)
self.client.get('/admin/admin/logentry/1/change/')
# 自定义分页测试方法验证分页逻辑和分页URL的可达性
def check_pagination(self, p, type, value):
# 遍历所有分页页面从第1页到最后一页
for page in range(1, p.num_pages + 1):
# 调用模板标签load_pagination_info获取分页信息上一页URL、下一页URL等
s = load_pagination_info(p.page(page), type, value)
self.assertIsNotNone(s) # 验证分页信息不为空
# 若存在上一页URL模拟访问并验证响应状态码为200
if s['previous_url']:
response = self.client.get(s['previous_url'])
self.assertEqual(response.status_code, 200)
# 若存在下一页URL模拟访问并验证响应状态码为200
if s['next_url']:
response = self.client.get(s['next_url'])
self.assertEqual(response.status_code, 200)
# 测试方法:测试图片上传功能(含未授权上传、授权上传验证)
def test_image(self):
# 导入requests库需提前安装用于下载测试图片
import requests
# 下载Python官网logo图片作为测试上传文件
rsp = requests.get(
'https://www.python.org/static/img/python-logo@2x.png')
# 定义图片保存路径:项目根目录下的'python.png'
imagepath = os.path.join(settings.BASE_DIR, 'python.png')
# 将下载的图片内容写入本地文件
with open(imagepath, 'wb') as file:
file.write(rsp.content)
# 模拟未授权的图片上传请求未带签名验证响应状态码为403禁止访问
rsp = self.client.post('/upload')
self.assertEqual(rsp.status_code, 403)
# 生成上传授权签名双重SHA256加密项目SECRET_KEY与后端上传接口的签名验证逻辑一致
sign = get_sha256(get_sha256(settings.SECRET_KEY))
# 读取本地测试图片文件创建SimpleUploadedFile对象模拟文件上传数据
with open(imagepath, 'rb') as file:
imgfile = SimpleUploadedFile(
'python.png', file.read(), content_type='image/jpg') # 文件名、文件内容、MIME类型
form_data = {'python.png': imgfile} # 构造表单数据(键为文件名,值为文件对象)
# 模拟带签名的图片上传请求跟随重定向follow=True
rsp = self.client.post(
'/upload?sign=' + sign, form_data, follow=True)
self.assertEqual(rsp.status_code, 200) # 验证授权上传成功响应状态码为200
# 删除本地测试图片文件(清理测试残留)
os.remove(imagepath)
# 测试工具函数:保存用户头像、发送邮件
from djangoblog.utils import save_user_avatar, send_email
send_email(['qq@qq.com'], 'testTitle', 'testContent') # 发送测试邮件(收件人、标题、内容)
# 从URL保存用户头像测试图片下载与保存逻辑
save_user_avatar(
'https://www.python.org/static/img/python-logo@2x.png')
# 测试方法测试错误页面404页面
def test_errorpage(self):
# 模拟访问不存在的URL'/eee'验证响应状态码为404页面未找到
rsp = self.client.get('/eee')
self.assertEqual(rsp.status_code, 404)
# 测试方法:测试自定义管理命令(如重建索引、清理缓存等)
def test_commands(self):
# 创建/获取测试用户与test_validate_article方法一致
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
username="liangliangyy")[0]
user.set_password("liangliangyy")
user.is_staff = True
user.is_superuser = True
user.save()
# 创建第三方登录配置QQ登录
c = OAuthConfig()
c.type = 'qq' # 登录类型QQ
c.appkey = 'appkey' # 测试用appkey
c.appsecret = 'appsecret' # 测试用appsecret
c.save() # 保存到数据库
# 创建第三方登录用户(关联测试用户)
u = OAuthUser()
u.type = 'qq' # 登录类型QQ
u.openid = 'openid' # 测试用openid第三方平台用户唯一标识
u.user = user # 关联本地测试用户
u.picture = static("/blog/img/avatar.png") # 静态文件中的默认头像
u.metadata = '''
{
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
}''' # 第三方用户元数据如QQ头像URL
u.save() # 保存到数据库
# 创建另一个第三方登录用户不关联本地用户测试头像URL直接赋值
u = OAuthUser()
u.type = 'qq' # 登录类型QQ
u.openid = 'openid1' # 不同的openid
# 直接赋值头像URL而非静态文件
u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
u.metadata = '''
{
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
}''' # 第三方用户元数据
u.save() # 保存到数据库
# 导入Elasticsearch启用标识
from blog.documents import ELASTICSEARCH_ENABLED
# 若启用Elasticsearch执行重建搜索索引命令
if ELASTICSEARCH_ENABLED:
call_command("build_index")
# 执行自定义管理命令:通知百度爬虫(参数"all"表示通知所有文章)
call_command("ping_baidu", "all")
# 执行自定义管理命令:创建测试数据
call_command("create_testdata")
# 执行自定义管理命令:清理缓存
call_command("clear_cache")
# 执行自定义管理命令:同步用户头像(从第三方平台同步头像到本地)
call_command("sync_user_avatar")
# 执行自定义管理命令:构建搜索关键词(优化搜索体验)
call_command("build_search_words")