|
|
# 导入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. TestCase:Django测试基类,提供测试环境初始化、断言方法等
|
|
|
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") |