ZetCode

测试重定向与转发数据

最后修改于 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.pyapp/ 文件夹包含核心文件,包括每个页面的模板以及计数器数据。

tests/ 文件夹包含用于 Selenium 测试的 test_navigation.py,用于验证计数器在重定向和转发时的行为。不使用数据库,将重点放在导航和数据上。

Flask 应用设置

run.py 作为启动 Flask 应用的入口点,利用应用程序工厂模式进行实例化。

run.py
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) 命令启动了启用了调试模式的开发服务器,这有助于在开发过程中进行实时错误跟踪和自动重新加载。

app/__init__.py
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 模板是应用程序的入口点,在 / 处渲染。

app/templates/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 处访问。

app/templates/redir.html
<!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 处为转发操作渲染的。

app/templates/fwd.html
<!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 文件使用名为 mainBlueprint 定义了应用的逻辑。

app/routes.py
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 测试导航。

tests/test_navigation.py
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(同一请求),并显示转发页面的内容。

运行应用和测试

现在我们展示如何运行应用和测试。

requirements.txt
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 标志运行测试以查看详细输出,确认重定向如何递增计数器而转发如何保留它。

作者

我叫 Jan Bodnar,我是一位充满热情的程序员,拥有多年的丰富经验。自 2007 年以来,我撰写了超过 1400 篇编程文章和 8 本电子书。此外,我在教授编程概念方面拥有超过八年的经验。

列出 所有 Python 教程