# 导入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")