第15章:测试
15.2 集成测试
LiveServerTestCase
LiveServerTestCase用于运行需要真实HTTP服务器的集成测试:
python
# test_integration.py
from django.test import LiveServerTestCase
from django.contrib.auth.models import User
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import time
class BlogIntegrationTest(LiveServerTestCase):
"""博客集成测试"""
@classmethod
def setUpClass(cls):
"""测试类初始化"""
super().setUpClass()
# 配置Chrome选项
chrome_options = Options()
chrome_options.add_argument('--headless') # 无头模式
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
# 启动Chrome浏览器
cls.browser = webdriver.Chrome(options=chrome_options)
cls.browser.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
"""测试类清理"""
cls.browser.quit()
super().tearDownClass()
def setUp(self):
"""每个测试前的设置"""
# 创建测试用户
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
self.user.save()
# 创建测试文章
from .models import Article, Category
self.category = Category.objects.create(name='测试分类')
self.article = Article.objects.create(
title='测试文章',
author=self.user,
category=self.category,
content='这是测试文章的内容',
status='published'
)
super().setUp()
def test_user_can_view_article_list(self):
"""测试用户可以查看文章列表"""
# 访问文章列表页面
self.browser.get(f'{self.live_server_url}/')
# 等待页面加载
WebDriverWait(self.browser, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "h1"))
)
# 检查页面标题
self.assertIn('博客', self.browser.title)
# 检查文章标题是否显示
page_text = self.browser.find_element(By.TAG_NAME, "body").text
self.assertIn('测试文章', page_text)
def test_user_can_view_article_detail(self):
"""测试用户可以查看文章详情"""
# 访问文章详情页面
self.browser.get(
f'{self.live_server_url}/articles/{self.article.slug}/'
)
# 等待页面加载
WebDriverWait(self.browser, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "h1"))
)
# 检查文章标题
title_element = self.browser.find_element(By.TAG_NAME, "h1")
self.assertEqual(title_element.text, '测试文章')
# 检查文章内容
content_element = self.browser.find_element(By.CLASS_NAME, "article-content")
self.assertIn('这是测试文章的内容', content_element.text)
def test_user_can_login_and_create_article(self):
"""测试用户可以登录并创建文章"""
# 访问登录页面
self.browser.get(f'{self.live_server_url}/accounts/login/')
# 填写登录表单
username_input = self.browser.find_element(By.NAME, "username")
password_input = self.browser.find_element(By.NAME, "password")
login_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
username_input.send_keys('testuser')
password_input.send_keys('testpass123')
login_button.click()
# 等待登录成功并重定向
WebDriverWait(self.browser, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "alert-success"))
)
# 访问创建文章页面
self.browser.get(f'{self.live_server_url}/articles/new/')
# 填写文章表单
title_input = self.browser.find_element(By.NAME, "title")
content_input = self.browser.find_element(By.NAME, "content")
submit_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
title_input.send_keys('新测试文章')
content_input.send_keys('这是新测试文章的内容')
submit_button.click()
# 等待文章创建成功
WebDriverWait(self.browser, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "alert-success"))
)
# 验证文章已创建
self.assertIn('文章创建成功', self.browser.page_source)
# 使用Playwright进行更现代的浏览器测试
# test_playwright.py
import pytest
from django.test import LiveServerTestCase
from playwright.sync_api import sync_playwright
class PlaywrightBlogTest(LiveServerTestCase):
"""使用Playwright的博客测试"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.playwright = sync_playwright().start()
cls.browser = cls.playwright.chromium.launch(headless=True)
cls.page = cls.browser.new_page()
@classmethod
def tearDownClass(cls):
cls.browser.close()
cls.playwright.stop()
super().tearDownClass()
def test_article_search_functionality(self):
"""测试文章搜索功能"""
# 创建测试数据
from .models import Article, Category
from django.contrib.auth.models import User
user = User.objects.create_user(
username='testuser',
password='testpass123'
)
category = Category.objects.create(name='测试分类')
Article.objects.create(
title='Python编程指南',
author=user,
category=category,
content='Python是一门强大的编程语言',
status='published'
)
Article.objects.create(
title='JavaScript入门',
author=user,
category=category,
content='JavaScript是前端开发的核心语言',
status='published'
)
# 访问首页
self.page.goto(f"{self.live_server_url}/")
# 使用搜索功能
search_input = self.page.locator("input[name='q']")
search_input.fill("Python")
search_input.press("Enter")
# 验证搜索结果
self.page.wait_for_selector(".article-list")
search_results = self.page.locator(".article-item")
assert search_results.count() == 1
assert "Python编程指南" in self.page.content()Selenium集成
Selenium集成测试的更多示例:
python
# test_selenium_advanced.py
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
class AdvancedSeleniumTest(LiveServerTestCase):
"""高级Selenium测试"""
def setUp(self):
"""测试设置"""
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
self.browser = webdriver.Chrome(options=chrome_options)
self.browser.implicitly_wait(10)
# 创建测试数据
from django.contrib.auth.models import User
from .models import Article, Category, Comment
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'
)
super().setUp()
def tearDown(self):
"""测试清理"""
self.browser.quit()
super().tearDown()
def test_comment_system(self):
"""测试评论系统"""
# 登录
self.browser.get(f'{self.live_server_url}/accounts/login/')
self.browser.find_element(By.NAME, "username").send_keys('testuser')
self.browser.find_element(By.NAME, "password").send_keys('testpass123')
self.browser.find_element(By.XPATH, "//button[@type='submit']").click()
# 访问文章详情
self.browser.get(
f'{self.live_server_url}/articles/{self.article.slug}/'
)
# 添加评论
comment_textarea = self.browser.find_element(By.NAME, "content")
comment_textarea.send_keys('这是一条测试评论')
submit_button = self.browser.find_element(
By.XPATH, "//button[contains(text(), '发表评论')]"
)
submit_button.click()
# 验证评论已添加
WebDriverWait(self.browser, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "comment-content"))
)
comment_content = self.browser.find_element(
By.CLASS_NAME, "comment-content"
).text
self.assertIn('这是一条测试评论', comment_content)
def test_responsive_design(self):
"""测试响应式设计"""
# 测试桌面版本
self.browser.set_window_size(1920, 1080)
self.browser.get(f'{self.live_server_url}/')
# 检查桌面导航
desktop_nav = self.browser.find_element(By.CLASS_NAME, "navbar-desktop")
self.assertTrue(desktop_nav.is_displayed())
# 测试移动版本
self.browser.set_window_size(375, 667)
self.browser.refresh()
# 检查移动导航
mobile_nav_toggle = self.browser.find_element(By.CLASS_NAME, "navbar-toggle")
self.assertTrue(mobile_nav_toggle.is_displayed())
def test_form_validation(self):
"""测试表单验证"""
# 登录
self.browser.get(f'{self.live_server_url}/accounts/login/')
self.browser.find_element(By.NAME, "username").send_keys('testuser')
self.browser.find_element(By.NAME, "password").send_keys('testpass123')
self.browser.find_element(By.XPATH, "//button[@type='submit']").click()
# 访问创建文章页面
self.browser.get(f'{self.live_server_url}/articles/new/')
# 提交空表单
submit_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
submit_button.click()
# 验证错误消息
error_messages = self.browser.find_elements(By.CLASS_NAME, "error-message")
self.assertGreater(len(error_messages), 0)
# 填写有效数据并提交
self.browser.find_element(By.NAME, "title").send_keys('有效标题')
self.browser.find_element(By.NAME, "content").send_keys('有效内容')
submit_button.click()
# 验证成功消息
success_message = self.browser.find_element(By.CLASS_NAME, "alert-success")
self.assertTrue(success_message.is_displayed())测试覆盖率
配置和使用测试覆盖率工具:
python
# 安装coverage工具
# pip install coverage
# .coveragerc 配置文件
"""
[run]
source = .
omit =
*/venv/*
*/env/*
*/migrations/*
*/tests/*
manage.py
*/settings/*
*/wsgi.py
*/asgi.py
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
[html]
directory = coverage_html_report
"""
# 运行带覆盖率的测试
# coverage run --source='.' manage.py test
# coverage report
# coverage html
# pytest配置文件 pytest.ini
"""
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
addopts = --cov=. --cov-report=html --cov-report=term
"""
# 使用pytest进行测试
# test_pytest.py
import pytest
from django.test import Client
from django.contrib.auth.models import User
from .models import Article, Category
@pytest.fixture
def client():
"""测试客户端fixture"""
return Client()
@pytest.fixture
def user():
"""用户fixture"""
return User.objects.create_user(
username='testuser',
password='testpass123'
)
@pytest.fixture
def article(user):
"""文章fixture"""
category = Category.objects.create(name='测试分类')
return Article.objects.create(
title='测试文章',
author=user,
category=category,
content='测试内容',
status='published'
)
def test_article_list_view(client, article):
"""测试文章列表视图"""
response = client.get('/')
assert response.status_code == 200
assert '测试文章' in str(response.content)
def test_article_detail_view(client, article):
"""测试文章详情视图"""
response = client.get(f'/articles/{article.slug}/')
assert response.status_code == 200
assert '测试文章' in str(response.content)
@pytest.mark.django_db
def test_article_creation():
"""测试文章创建"""
user = User.objects.create_user(
username='testuser',
password='testpass123'
)
category = Category.objects.create(name='测试分类')
article = Article.objects.create(
title='新文章',
author=user,
category=category,
content='新文章内容',
status='published'
)
assert article.title == '新文章'
assert article.is_published
# 性能测试示例
import time
from django.test import TestCase
class PerformanceTest(TestCase):
"""性能测试"""
def setUp(self):
# 创建大量测试数据
from django.contrib.auth.models import User
from .models import Article, Category
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.category = Category.objects.create(name='测试分类')
# 创建1000篇文章用于性能测试
for i in range(1000):
Article.objects.create(
title=f'测试文章{i}',
author=self.user,
category=self.category,
content=f'测试内容{i}',
status='published'
)
def test_article_list_performance(self):
"""测试文章列表性能"""
start_time = time.time()
# 执行100次查询
for _ in range(100):
response = self.client.get('/articles/')
self.assertEqual(response.status_code, 200)
end_time = time.time()
total_time = end_time - start_time
# 断言平均响应时间小于100ms
average_time = total_time / 100
self.assertLess(average_time, 0.1)持续集成
配置持续集成环境:
yaml
# .github/workflows/test.yml
name: Django CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:6
ports:
- 6379:6379
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install coverage
- name: Run migrations
run: python manage.py migrate
- name: Run tests with coverage
run: coverage run --source='.' manage.py test
- name: Generate coverage report
run: |
coverage report
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella通过这些集成测试实践,可以确保应用程序在真实环境中的功能和性能表现符合预期。
小结
Django集成测试的核心要点:
- ✅ 使用LiveServerTestCase进行浏览器集成测试
- ✅ 掌握Selenium和Playwright等工具的使用
- ✅ 实施测试覆盖率分析确保代码质量
- ✅ 配置持续集成环境自动化测试流程
- ✅ 关注性能测试和响应时间优化
集成测试能够验证系统各组件间的协作是否正常。
下一篇
我们将学习数据库优化技术。