@ -1,9 +1,5 @@
# bjy: 导入操作系统接口模块
import json
import os
from unittest . mock import patch , MagicMock
# bjy: 从Django中导入设置、文件上传、命令调用、分页器、静态文件、测试工具、URL反向解析和时区工具
from django . conf import settings
from django . core . files . uploadedfile import SimpleUploadedFile
from django . core . management import call_command
@ -13,41 +9,30 @@ from django.test import Client, RequestFactory, TestCase
from django . urls import reverse
from django . utils import timezone
# bjy: 从项目中导入用户模型、博客表单、博客模型、自定义模板标签和工具函数
from accounts . models import BlogUser
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
from blog . views import LikeArticle
from djangoblog . utils import get_current_site , get_sha256
# bjy: 从项目中导入OAuth相关模型
from oauth . models import OAuthUser , OAuthConfig
# bjy: 在此处创建测试。
# Create your tests here.
# bjy: 定义一个针对文章功能的测试类, 继承自Django的TestCase
class ArticleTest ( TestCase ) :
# bjy: setUp方法在每个测试方法执行前运行, 用于初始化测试环境
def setUp ( self ) :
# bjy: 创建一个测试客户端实例,用于模拟浏览器请求
self . client = Client ( )
# bjy: 创建一个请求工厂实例,用于生成请求对象
self . factory = RequestFactory ( )
# bjy: 定义一个测试方法,用于验证文章相关的功能
def test_validate_article ( self ) :
site = get_current_site ( ) . domain
user = BlogUser . objects . get_or_create (
email = " liangliangyy@gmail.com " ,
username = " liangliangyy " ) [ 0 ]
# bjy: 设置用户密码
user . set_password ( " liangliangyy " )
# bjy: 设置用户为员工和管理员
user . is_staff = True
user . is_superuser = True
user . save ( )
# bjy: 测试用户详情页是否能正常访问
response = self . client . get ( user . get_absolute_url ( ) )
self . assertEqual ( response . status_code , 200 )
response = self . client . get ( ' /admin/servermanager/emailsendlog/ ' )
@ -59,19 +44,16 @@ class ArticleTest(TestCase):
s . is_enable = True
s . save ( )
# bjy: 创建并保存一个分类实例
category = Category ( )
category . name = " category "
category . creation_time = timezone . now ( )
category . last_mod_time = timezone . now ( )
category . save ( )
# bjy: 创建并保存一个标签实例
tag = Tag ( )
tag . name = " nicetag "
tag . save ( )
# bjy: 创建并保存一篇文章
article = Article ( )
article . title = " nicetitle "
article . body = " nicecontent "
@ -81,15 +63,11 @@ class ArticleTest(TestCase):
article . status = ' p '
article . save ( )
# bjy: 验证文章初始标签数量为0
self . assertEqual ( 0 , article . tags . count ( ) )
# bjy: 给文章添加标签并保存
article . tags . add ( tag )
article . save ( )
# bjy: 验证文章标签数量变为1
self . assertEqual ( 1 , article . tags . count ( ) )
# bjy: 循环创建20篇文章用于测试分页等功能
for i in range ( 20 ) :
article = Article ( )
article . title = " nicetitle " + str ( i )
@ -101,126 +79,96 @@ class ArticleTest(TestCase):
article . save ( )
article . tags . add ( tag )
article . save ( )
# bjy: 如果启用了Elasticsearch, 则重建索引并测试搜索功能
from blog . documents import ELASTICSEARCH_ENABLED
if ELASTICSEARCH_ENABLED :
call_command ( " build_index " )
response = self . client . get ( ' /search ' , { ' q ' : ' nicetitle ' } )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试文章详情页
response = self . client . get ( article . get_absolute_url ( ) )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试蜘蛛通知功能
from djangoblog . spider_notify import SpiderNotify
SpiderNotify . notify ( article . get_absolute_url ( ) )
# bjy: 测试标签页
response = self . client . get ( tag . get_absolute_url ( ) )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试分类页
response = self . client . get ( category . get_absolute_url ( ) )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试搜索页
response = self . client . get ( ' /search ' , { ' q ' : ' django ' } )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试加载文章标签的模板标签
s = load_articletags ( article )
self . assertIsNotNone ( s )
# bjy: 以超级用户身份登录
self . client . login ( username = ' liangliangyy ' , password = ' liangliangyy ' )
# bjy: 测试文章归档页
response = self . client . get ( reverse ( ' blog:archives ' ) )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试文章列表的分页信息
p = Paginator ( Article . objects . all ( ) , settings . PAGINATE_BY )
self . check_pagination ( p , ' ' , ' ' )
# bjy: 测试按标签筛选后的文章分页
p = Paginator ( Article . objects . filter ( tags = tag ) , settings . PAGINATE_BY )
self . check_pagination ( p , ' 分类标签归档 ' , tag . slug )
# bjy: 测试按作者筛选后的文章分页
p = Paginator (
Article . objects . filter (
author__username = ' liangliangyy ' ) , settings . PAGINATE_BY )
self . check_pagination ( p , ' 作者文章归档 ' , ' liangliangyy ' )
# bjy: 测试按分类筛选后的文章分页
p = Paginator ( Article . objects . filter ( category = category ) , settings . PAGINATE_BY )
self . check_pagination ( p , ' 分类目录归档 ' , category . slug )
# bjy: 测试搜索表单
f = BlogSearchForm ( )
f . search ( )
# bjy: 测试百度通知功能
# self.client.login(username='liangliangyy', password='liangliangyy')
from djangoblog . spider_notify import SpiderNotify
SpiderNotify . baidu_notify ( [ article . get_full_url ( ) ] )
# bjy: 测试获取Gravatar头像的模板标签
from blog . templatetags . blog_tags import gravatar_url , gravatar
u = gravatar_url ( ' liangliangyy@gmail.com ' )
u = gravatar ( ' liangliangyy@gmail.com ' )
# bjy: 创建并保存一个友情链接
link = Links (
sequence = 1 ,
name = " lylinux " ,
link = ' https://wwww.lylinux.net ' )
link . save ( )
# bjy: 测试友情链接页面
response = self . client . get ( ' /links.html ' )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试RSS订阅页面
response = self . client . get ( ' /feed/ ' )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试网站地图
response = self . client . get ( ' /sitemap.xml ' )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试一些Admin后台的删除和变更操作
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/ ' )
# bjy: 辅助方法,用于检查分页导航链接是否正确
def check_pagination ( self , p , type , value ) :
# bjy: 遍历所有页码
for page in range ( 1 , p . num_pages + 1 ) :
# bjy: 加载当前页的分页信息
s = load_pagination_info ( p . page ( page ) , type , value )
self . assertIsNotNone ( s )
# bjy: 如果存在上一页链接,则测试其可访问性
if s [ ' previous_url ' ] :
response = self . client . get ( s [ ' previous_url ' ] )
self . assertEqual ( response . status_code , 200 )
# bjy: 如果存在下一页链接,则测试其可访问性
if s [ ' next_url ' ] :
response = self . client . get ( s [ ' next_url ' ] )
self . assertEqual ( response . status_code , 200 )
# bjy: 测试图片上传功能
def test_image ( self ) :
# bjy: 下载一个网络图片到本地
import requests
rsp = requests . get (
' https://www.python.org/static/img/python-logo.png ' )
imagepath = os . path . join ( settings . BASE_DIR , ' python.png ' )
with open ( imagepath , ' wb ' ) as file :
file . write ( rsp . content )
# bjy: 测试无签名上传, 预期返回403
rsp = self . client . post ( ' /upload ' )
self . assertEqual ( rsp . status_code , 403 )
# bjy: 生成签名
sign = get_sha256 ( get_sha256 ( settings . SECRET_KEY ) )
# bjy: 使用签名上传图片
with open ( imagepath , ' rb ' ) as file :
imgfile = SimpleUploadedFile (
' python.png ' , file . read ( ) , content_type = ' image/jpg ' )
@ -228,23 +176,17 @@ class ArticleTest(TestCase):
rsp = self . client . post (
' /upload?sign= ' + sign , form_data , follow = True )
self . assertEqual ( rsp . status_code , 200 )
# bjy: 删除本地临时图片
os . remove ( imagepath )
# bjy: 测试发送邮件和保存用户头像的工具函数
from djangoblog . utils import save_user_avatar , send_email
send_email ( [ ' qq@qq.com ' ] , ' testTitle ' , ' testContent ' )
save_user_avatar (
' https://www.python.org/static/img/python-logo.png ' )
# bjy: 测试404错误页面
def test_errorpage ( self ) :
rsp = self . client . get ( ' /eee ' )
self . assertEqual ( rsp . status_code , 404 )
# bjy: 测试自定义的管理命令
@staticmethod
def test_commands ( ) :
# bjy: 创建一个超级用户(如果不存在)
def test_commands ( self ) :
user = BlogUser . objects . get_or_create (
email = " liangliangyy@gmail.com " ,
username = " liangliangyy " ) [ 0 ]
@ -253,14 +195,12 @@ class ArticleTest(TestCase):
user . is_superuser = True
user . save ( )
# bjy: 创建并保存一个OAuth配置
c = OAuthConfig ( )
c . type = ' qq '
c . appkey = ' appkey '
c . appsecret = ' appsecret '
c . save ( )
# bjy: 创建并保存一个OAuth用户, 关联到超级用户
u = OAuthUser ( )
u . type = ' qq '
u . openid = ' openid '
@ -272,7 +212,6 @@ class ArticleTest(TestCase):
} '''
u . save ( )
# bjy: 创建另一个OAuth用户, 用于测试
u = OAuthUser ( )
u . type = ' qq '
u . openid = ' openid1 '
@ -283,294 +222,11 @@ class ArticleTest(TestCase):
} '''
u . save ( )
# bjy: 如果启用了Elasticsearch, 则重建索引
from blog . documents import ELASTICSEARCH_ENABLED
if ELASTICSEARCH_ENABLED :
call_command ( " build_index " )
# bjy: 调用并测试一系列自定义管理命令
call_command ( " ping_baidu " , " all " )
call_command ( " create_testdata " )
call_command ( " clear_cache " )
call_command ( " sync_user_avatar " )
call_command ( " build_search_words " )
class TestLikeArticle ( TestCase ) :
""" 测试 LikeArticle 视图类中的 post 方法 """
def setUp ( self ) :
"""
初始化测试所需的数据和工具
"""
self . factory = RequestFactory ( )
self . user = BlogUser . objects . create_user ( username = ' testuser ' , password = ' password ' )
# 创建分类( Article模型需要category字段)
self . category = Category . objects . create (
name = " Test Category " ,
slug = " test-category "
)
self . article = Article . objects . create (
title = " Test Article " ,
body = " This is a test article. " ,
author = self . user ,
category = self . category , # Article模型必需字段
views = 0 ,
)
@patch ( ' blog.models.Article.objects.get ' )
def test_post_like_article_successfully ( self , mock_get_article ) :
"""
测试场景 : 用户第一次点赞文章成功
输入 :
- 已登录用户
- 存在的文章 ID
- 用户尚未点赞该文章
输出 :
- type = 1 表示新增点赞
- like_sum 更新为 1
- state = 200 成功状态码
"""
# 设置 mock 返回值
mock_article = MagicMock ( )
mock_article . users_like . filter . return_value . exists . return_value = False
mock_article . users_like . count . return_value = 1
mock_get_article . return_value = mock_article
# 构造 POST 请求
request = self . factory . post ( ' /like/ ' , { ' article_id ' : str ( self . article . id ) } )
request . user = self . user
# 执行被测函数
response = LikeArticle ( ) . post ( request )
# 断言调用了 add 方法表示点赞
mock_article . users_like . add . assert_called_once_with ( self . user )
mock_article . users_like . remove . assert_not_called ( )
# 解析响应内容
content = json . loads ( response . content . decode ( ) )
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( content [ ' type ' ] , 1 )
self . assertEqual ( content [ ' like_sum ' ] , 1 )
self . assertEqual ( content [ ' state ' ] , 200 )
@patch ( ' blog.models.Article.objects.get ' )
def test_post_unlike_article_successfully ( self , mock_get_article ) :
"""
测试场景 : 用户取消点赞文章成功
输入 :
- 已登录用户
- 存在的文章 ID
- 用户已经点赞了该文章
输出 :
- type = 0 表示取消点赞
- like_sum 更新为 0
- state = 200 成功状态码
"""
# 设置 mock 返回值
mock_article = MagicMock ( )
mock_article . users_like . filter . return_value . exists . return_value = True
mock_article . users_like . count . return_value = 0
mock_get_article . return_value = mock_article
# 构造 POST 请求
request = self . factory . post ( ' /like/ ' , { ' article_id ' : str ( self . article . id ) } )
request . user = self . user
# 执行被测函数
response = LikeArticle ( ) . post ( request )
# 断言调用了 remove 方法表示取消点赞
mock_article . users_like . remove . assert_called_once_with ( self . user )
mock_article . users_like . add . assert_not_called ( )
# 解析响应内容
content = json . loads ( response . content . decode ( ) )
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( content [ ' type ' ] , 0 )
self . assertEqual ( content [ ' like_sum ' ] , 0 )
self . assertEqual ( content [ ' state ' ] , 200 )
@patch ( ' blog.models.Article.objects.get ' )
def test_post_article_does_not_exist ( self , mock_get_article ) :
"""
测试场景 : 提供的文章 ID 不存在
输入 :
- 任意用户
- 不存在的文章 ID
输出 :
- state = 400 错误状态码
- data 包含 “ 文章不存在 ” 提示
"""
# 设置 mock 抛出 DoesNotExist 异常
mock_get_article . side_effect = Article . DoesNotExist
# 构造 POST 请求
request = self . factory . post ( ' /like/ ' , { ' article_id ' : ' 999 ' } )
request . user = self . user
# 执行被测函数
response = LikeArticle ( ) . post ( request )
# 解析响应内容
content = json . loads ( response . content . decode ( ) )
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( content [ ' state ' ] , 400 )
self . assertIn ( " 文章不存在 " , content [ ' data ' ] )
@patch ( ' blog.models.Article.objects.get ' )
def test_post_internal_server_error ( self , mock_get_article ) :
"""
测试场景 : 系统内部发生异常
输入 :
- 任意用户
- 导致异常的操作 ( 如数据库连接失败等 )
输出 :
- state = 500 错误状态码
- data 包含具体异常描述
"""
# 设置 mock 抛出通用异常
mock_get_article . side_effect = Exception ( " 数据库连接超时 " )
# 构造 POST 请求
request = self . factory . post ( ' /like/ ' , { ' article_id ' : str ( self . article . id ) } )
request . user = self . user
# 执行被测函数
response = LikeArticle ( ) . post ( request )
# 解析响应内容
content = json . loads ( response . content . decode ( ) )
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( content [ ' state ' ] , 500 )
self . assertIn ( " 服务器错误 " , content [ ' data ' ] )
class LikeIntegrationTests ( TestCase ) :
def setUp ( self ) :
""" 设置测试数据 """
self . client = Client ( )
self . user = BlogUser . objects . create_user (
username = ' testuser ' ,
email = ' test@example.com ' ,
password = ' testpass123 '
)
self . other_user = BlogUser . objects . create_user (
username = ' otheruser ' ,
email = ' other@example.com ' ,
password = ' testpass123 '
)
# 创建分类( 因为Article模型需要category字段)
self . category = Category . objects . create (
name = ' 测试分类 ' ,
slug = ' test-category '
)
self . article = Article . objects . create (
title = ' Test Article ' ,
body = ' Test content ' ,
author = self . user ,
category = self . category , # 必须提供category
# 其他必填字段使用默认值
status = ' p ' , # 发布状态
comment_status = ' o ' , # 开放评论
type = ' a ' , # 文章类型
article_order = 0 ,
show_toc = False
)
def test_like_workflow ( self ) :
""" 测试完整的点赞流程 """
# 1. 用户登录
self . client . login ( username = ' testuser ' , password = ' testpass123 ' )
# 2. 发送点赞请求
response = self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : self . article . id } ,
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
# 3. 验证响应
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( response . json ( ) [ ' type ' ] , 1 ) # 点赞操作
# 4. 验证数据库状态
self . assertTrue ( self . article . users_like . filter ( id = self . user . id ) . exists ( ) )
self . assertEqual ( self . article . users_like . count ( ) , 1 )
# 5. 测试取消点赞
response = self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : self . article . id } ,
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
# 6. 验证取消点赞
self . assertEqual ( response . json ( ) [ ' type ' ] , 0 ) # 取消点赞操作
self . assertFalse ( self . article . users_like . filter ( id = self . user . id ) . exists ( ) )
self . assertEqual ( self . article . users_like . count ( ) , 0 )
def test_multiple_users_liking ( self ) :
""" 测试多个用户点赞同一篇文章 """
# 第一个用户点赞
self . client . login ( username = ' testuser ' , password = ' testpass123 ' )
self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : self . article . id } ,
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
# 第二个用户点赞
self . client . login ( username = ' otheruser ' , password = ' testpass123 ' )
response = self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : self . article . id } ,
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
# 验证两个用户都点赞成功
self . assertEqual ( self . article . users_like . count ( ) , 2 )
self . assertTrue ( self . article . users_like . filter ( id = self . user . id ) . exists ( ) )
self . assertTrue ( self . article . users_like . filter ( id = self . other_user . id ) . exists ( ) )
def test_like_nonexistent_article ( self ) :
""" 测试给不存在的文章点赞 """
self . client . login ( username = ' testuser ' , password = ' testpass123 ' )
response = self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : 1145 } , # 不存在的文章ID
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
self . assertEqual ( response . status_code , 400 )
self . assertIn ( ' 文章不存在 ' , response . json ( ) [ ' data ' ] )
def test_like_without_login ( self ) :
""" 测试未登录用户点赞 """
response = self . client . post (
reverse ( ' blog:like_article ' ) ,
{ ' article_id ' : self . article . id } ,
HTTP_X_REQUESTED_WITH = ' XMLHttpRequest '
)
# 应该重定向到登录页面或者返回错误
self . assertIn ( response . status_code , [ 302 , 403 ] ) # 重定向或权限拒绝
def test_like_with_invalid_method ( self ) :
""" 测试使用错误的HTTP方法 """
self . client . login ( username = ' testuser ' , password = ' testpass123 ' )
response = self . client . get ( reverse ( ' blog:like_article ' ) ) # 使用GET而不是POST
self . assertEqual ( response . status_code , 405 ) # Method Not Allowed