测试重定向与转发数据
最后修改于 2025 年 3 月 23 日
本指南将构建一个带有重定向和转发导航的 Flask 应用,通过一个计数器来展示数据处理的差异。
重定向与转发
redirect
(重定向)和 forward(转发)之间的区别在于服务器和客户端如何处理导航。重定向会指示客户端(通常是浏览器)向另一个 URL 发起新的 HTTP 请求。这涉及一个往返过程,客户端会暂时暂停并直接访问新资源,这通常会导致浏览器 URL 的变化。
相比之下,**转发**完全在服务器端进行,服务器会内部将处理转移到另一个资源(如不同的 servlet 或视图),而客户端并不知道这种变化。通过转发,浏览器中的 URL 保持不变,用户不会注意到内部路由,这使得它无缝且适合内部导航。
简介
Flask 是一个通用的 Python Web 框架。在这里,我们将探索重定向(新 URL)和转发(同一 URL,新内容)导航,并通过跟踪计数器来突出显示数据在每种情况下的行为,并使用 Selenium 和 unittest 进行测试。
项目结构
以下是应用程序的结构。
redirect_forward_app/ ├── requirements.txt # Dependencies ├── run.py # App entry point ├── app/ │ ├── __init__.py # App factory │ ├── routes.py # Routes │ └── templates/ │ ├── home.html # Home page │ ├── redir.html # Redirect target │ └── fwd.html # Forward target └── tests/ └── test_navigation.py # Selenium tests
结构很简单。根目录包含用于依赖项的 requirements.txt
和启动应用的 run.py
。app/
文件夹包含核心文件,包括每个页面的模板以及计数器数据。
tests/
文件夹包含用于 Selenium 测试的 test_navigation.py
,用于验证计数器在重定向和转发时的行为。不使用数据库,将重点放在导航和数据上。
Flask 应用设置
此 run.py
作为启动 Flask 应用的入口点,利用应用程序工厂模式进行实例化。
from flask_pagination import create_app if __name__ == '__main__': app = create_app() app.run(debug=True)
run.py
文件是启动 Flask 应用程序的主要可执行文件。它从 flask_pagination 包导入 create_app
函数并调用它来实例化应用程序对象。app.run(debug=True)
命令启动了启用了调试模式的开发服务器,这有助于在开发过程中进行实时错误跟踪和自动重新加载。
from flask import Flask, session def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = 'simple-secret-for-testing' from . import routes app.register_blueprint(routes.bp) return app
create_app
函数设置 Flask 应用。它导入 session
以在请求之间存储计数器。设置了 SECRET_KEY
以确保会话安全,这对于持久化计数器等数据至关重要。
该函数注册一个来自 routes.py
的 Blueprint 来定义端点。这个最小化的设置侧重于导航和数据处理,避免了本演示不必要的复杂性。
主页模板
home.html
模板是应用程序的入口点,在 /
处渲染。
<!DOCTYPE html> <html> <head> <title>Home</title> </head> <body> <h1>Home Page</h1> <p>Counter: {{ counter }}</p> <a href="{{ url_for('main.redirect_page') }}">Go to Redirect</a> <br> <a href="{{ url_for('main.forward_page') }}">Go to Forward</a> </body> </html>
它显示一个标题、从路由传递过来的当前 counter
值,以及使用 url_for
触发重定向和转发操作的链接。
每次访问主页时,计数器都会递增,并存储在 session 中。这个设置使我们能够看到重定向(新请求)如何重置计数器,而转发(同一请求)如何保留它,从而说明它们之间的数据差异。
重定向目标模板
redir.html
模板是重定向的目标,在 /redir-target
处访问。
<!DOCTYPE html> <html> <head> <title>Redirected Page</title> </head> <body> <h1>You've been redirected here!</h1> <p>Counter: {{ counter }}</p> <a href="{{ url_for('main.home') }}">Back to Home</a> </body> </html>
它显示一条消息、counter
值和一个返回主页的链接。由于重定向触发了一个新请求,计数器在这里再次递增。
这种行为与转发形成对比,表明重定向涉及完整的客户端导航,重置请求特定数据,除非显式保留(例如通过 session),我们将使用 Selenium 进行测试。
转发目标模板
fwd.html
模板是在 /forward
处为转发操作渲染的。
<!DOCTYPE html> <html> <head> <title>Forwarded Page</title> </head> <body> <h1>You've been forwarded here!</h1> <p>Counter: {{ counter }}</p> <a href="{{ url_for('main.home') }}">Back to Home</a> </body> </html>
它显示一条消息、counter
值和一个主页链接。由于转发发生在同一个请求内,计数器不会超过主页的值。
计数器在同一请求中的这种持久性突显了与重定向的关键区别,重定向会触发额外的递增。测试将验证这种数据处理上的差异。
路由
routes.py
文件使用名为 main
的 Blueprint
定义了应用的逻辑。
from flask import Blueprint, render_template, redirect, url_for, session bp = Blueprint('main', __name__) @bp.route('/') def home(): counter = session.get('counter', 0) + 1 session['counter'] = counter return render_template('home.html', counter=counter) @bp.route('/redirect') def redirect_page(): return redirect(url_for('main.redirect_target')) @bp.route('/forward') def forward_page(): counter = session.get('counter', 0) return render_template('fwd.html', counter=counter) @bp.route('/redir-target') def redirect_target(): counter = session.get('counter', 0) + 1 session['counter'] = counter return render_template('redir.html', counter=counter)
/
路由在 session 中递增 counter
(从 0 开始)并使用该值渲染 home.html
。这为导航设置了基线。
/redirect
路由使用 redirect
将用户发送到 /redir-target
,在那里由于新请求计数器再次递增。/forward
直接渲染 fwd.html
,使用现有的 counter
而不递增,因为这是同一个请求。这些差异是理解重定向与转发的关键。
Selenium 测试
test_navigation.py
文件使用 unittest
和 Selenium 测试导航。
import unittest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import threading from app import create_app class TestNavigation(unittest.TestCase): def setUp(self): self.app = create_app() self.client = self.app.test_client() self.server_thread = threading.Thread( target=self.app.run, kwargs={'port': 5000}) self.server_thread.daemon = True self.server_thread.start() time.sleep(1) self.driver = webdriver.Chrome( service=Service(ChromeDriverManager().install())) self.driver.get('https://:5000') time.sleep(1) def tearDown(self): self.driver.quit() def test_redirect(self): driver = self.driver WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.TAG_NAME, "body"), "Counter: 1")) driver.find_element(By.LINK_TEXT, "Go to Redirect").click() WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.TAG_NAME, "body"), "Counter: 2")) self.assertEqual(driver.current_url, 'https://:5000/redir-target') self.assertIn("You've been redirected here!", driver.page_source) self.assertIn("Counter: 2", driver.page_source) def test_forward(self): driver = self.driver WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.TAG_NAME, "body"), "Counter: 1")) driver.find_element(By.LINK_TEXT, "Go to Forward").click() WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.TAG_NAME, "body"), "Counter: 1")) self.assertEqual(driver.current_url, 'https://:5000/forward') self.assertIn("You've been forwarded here!", driver.page_source) self.assertIn("Counter: 1", driver.page_source)
setUp
在端口 5000 的线程中启动 Flask 应用,并打开一个 Chrome 驱动程序到 /
,计数器从 1 开始。tearDown
关闭驱动程序。
test_redirect
验证点击“Go to Redirect”是否将 URL 更改为 /redir-target
,将计数器递增到 2(新请求),并显示预期的内容。test_forward
检查点击“Go to Forward”是否将 URL 保持在 /forward
,将计数器保持在 1(同一请求),并显示转发页面的内容。
运行应用和测试
现在我们展示如何运行应用和测试。
flask selenium webdriver-manager
$ pip install -r requirements.txt $ python run.py
运行测试
$ python -m unittest tests/test_navigation.py -v
使用 pip 从 requirements.txt
安装依赖项,然后使用 python run.py
启动应用。使用 unittest
和 -v
标志运行测试以查看详细输出,确认重定向如何递增计数器而转发如何保留它。
作者
列出 所有 Python 教程。