从零搭建Python Selenium自动化测试框架:POM设计与Pytest实践

发布时间:2026/7/2 23:18:22
从零搭建Python Selenium自动化测试框架:POM设计与Pytest实践 1. 项目概述为什么我们需要一个自己的自动化测试框架如果你是一名测试工程师或者正在学习Python自动化测试那么“Selenium”这个名字你一定不陌生。它就像一把万能钥匙能让你用代码控制浏览器模拟用户点击、输入、滚动等一系列操作。但很多朋友在入门后会发现仅仅会写几个find_element和click是远远不够的。当测试用例从几个变成几十个、几百个时脚本会变得混乱不堪重复的登录代码到处都是元素定位器散落在各个角落一个页面改了所有脚本都得跟着改维护成本高得吓人。这就是为什么我们需要一个“框架”。它不是一个遥不可及的概念而是一套帮你把散乱的积木单个测试脚本搭建成稳固城堡可维护、可扩展的测试体系的规则和工具。今天我们不谈那些庞大复杂的商业框架就用最接地气的Python Selenium PyCharm从零开始手把手搭建一个属于你自己的、五脏俱全的自动化测试框架。这个框架将涵盖用例管理、页面对象模型、数据驱动、日志报告等核心模块让你彻底告别“脚本小子”阶段写出真正专业、高效的自动化测试代码。无论你是刚学完Selenium基础的新手还是苦于脚本维护的老手这篇文章都将为你提供一条清晰的路径。2. 框架核心设计与思路拆解在动手写代码之前理清思路至关重要。一个好的框架不是功能的堆砌而是为了解决特定问题而设计的结构。我们的核心目标是提升脚本的可维护性、可读性和复用性。基于这个目标我们采用业界公认的最佳实践来设计框架的骨架。2.1 为什么选择“页面对象模型”作为基石页面对象模型Page Object Model POM是Selenium自动化测试的黄金法则。它的核心思想是将测试逻辑和页面元素定位与操作分离开。想象一下如果没有POM你的测试脚本会直接充斥着像driver.find_element(By.ID, “username”).send_keys(“admin”)这样的代码。一旦页面上用户名输入框的ID变了你就得在所有用到它的脚本里一个个去修改这无疑是场灾难。POM的做法是为每一个被测试的网页或页面中的一个功能区域创建一个对应的Python类。这个类不关心测试用例是什么它只做两件事封装元素定位器将所有这个页面的元素定位方式如ID、XPath、CSS选择器定义为类的属性。封装页面操作将在这个页面上可能进行的操作如输入、点击、获取文本定义为类的方法。这样测试用例脚本就变得非常清爽login_page.input_username(“admin”)、login_page.click_submit()。即使页面元素变了你也只需要去修改对应的Page类中的一个地方所有测试用例都会自动生效。这极大地降低了维护成本也使得非技术人员如产品经理能更容易地理解测试用例在做什么。2.2 目录结构规划清晰即高效一个清晰的目录结构是框架可维护性的物理体现。在PyCharm中新建一个项目我们建议按如下方式组织your_automation_framework/ ├── common/ # 公共组件 │ ├── __init__.py │ ├── base_page.py # 所有Page类的基类 │ ├── webdriver_utils.py # 浏览器驱动工具类 │ └── logger.py # 日志记录模块 ├── page_objects/ # 页面对象类 │ ├── __init__.py │ ├── login_page.py │ ├── home_page.py │ └── search_page.py ├── test_cases/ # 测试用例 │ ├── __init__.py │ ├── test_login.py │ └── test_search.py ├── test_data/ # 测试数据 │ ├── __init__.py │ └── data.json 或 data.csv ├── reports/ # 测试报告执行后生成 │ └── 存放HTML报告、截图等 ├── logs/ # 运行日志执行后生成 │ └── 存放.log文件 ├── conftest.py # Pytest的共享Fixture配置如果使用Pytest └── run_tests.py # 测试执行入口文件这个结构将不同职责的代码模块化各司其职。common放通用工具page_objects专注页面test_cases专注业务逻辑test_data管理输入reports和logs存放输出。无论项目规模如何增长你都能快速找到需要修改的代码。2.3 工具选型为什么是Pytest Allure虽然Python自带的unittest模块也能用但我们强烈推荐使用Pytest作为测试运行器。原因很简单更简洁、更强大、更灵活。Pytest的语法更符合Python风格不需要继承某个类夹具Fixture机制特别是pytest.fixture能优雅地管理测试前置如初始化浏览器和后置如关闭浏览器、截图操作参数化测试也异常方便。对于测试报告HTMLTestRunner是经典选择但Allure报告在美观度和信息呈现上更胜一筹。它能生成非常专业的交互式报告清晰地展示测试套件、用例层级、步骤详情、附件截图、日志等是向团队展示测试结果的有力工具。我们将把Allure集成进来作为框架的报告模块。注意工具选型没有绝对的对错关键是适合团队和项目。这里的选择是基于当前Python测试生态的主流和最佳实践能让你在学习和工作中与社区接轨。3. 核心模块详解与实操要点有了设计蓝图我们就可以开始一砖一瓦地搭建了。我们从最基础的、也是最重要的几个模块开始。3.1 环境准备与依赖安装工欲善其事必先利其器。确保你的电脑上已经安装了Python建议3.8及以上版本和PyCharm社区版即可。接下来我们通过命令行PyCharm的Terminal或系统CMD安装必要的Python库。# 核心测试库 pip install selenium # 测试运行与框架 pip install pytest pip install pytest-html # 生成简易HTML报告 pip install pytest-xdist # 可选用于并行测试 # 高级报告系统 pip install allure-pytest # 数据驱动支持如使用Excel pip install openpyxl pandas浏览器驱动管理这是新手最容易踩坑的地方。Selenium需要通过一个“驱动程序”如chromedriver来与实际的浏览器如Chrome对话。你需要根据本地安装的Chrome浏览器版本去下载对应版本的chromedriver。一个省事的做法是使用webdriver-manager库它可以自动下载和管理匹配的驱动。pip install webdriver-manager在代码中你可以这样使用它来避免手动下载和配置环境变量的麻烦from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver webdriver.Chrome(serviceService(ChromeDriverManager().install()))3.2 打造坚如磐石的BasePageBasePage是所有具体页面对象类如LoginPage的父类。它封装了所有页面都可能用到的最基本操作实现了“一次编写处处复用”。这是框架复用的核心。# common/base_page.py import logging from datetime import datetime from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class BasePage: 所有页面类的基类封装通用方法 def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.wait WebDriverWait(driver, 10) # 显式等待超时10秒 def find_element(self, locator): 查找单个元素加入显式等待和日志 try: self.logger.info(f正在查找元素: {locator}) element self.wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: self.logger.error(f元素查找超时: {locator}) self._take_screenshot(element_not_found) raise def click(self, locator): 点击元素 element self.find_element(locator) self.logger.info(f点击元素: {locator}) element.click() def input_text(self, locator, text): 向元素输入文本 element self.find_element(locator) self.logger.info(f向元素 {locator} 输入文本: {text}) element.clear() element.send_keys(text) def get_text(self, locator): 获取元素文本 element self.find_element(locator) text element.text self.logger.info(f获取元素 {locator} 的文本: {text}) return text def _take_screenshot(self, name): 内部方法截图并保存到报告目录 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_path f./reports/screenshots/{name}_{timestamp}.png self.driver.save_screenshot(screenshot_path) self.logger.info(f截图已保存至: {screenshot_path}) return screenshot_path def is_element_visible(self, locator, timeout5): 判断元素是否可见快速检查 try: WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator)) return True except TimeoutException: return False关键点解析显式等待WebDriverWait是处理页面元素加载异步问题的利器。比起time.sleep()这种“硬等待”它更智能会在条件满足如元素出现、可点击后立即继续最多等待指定时间。这能显著提升测试执行速度。集中日志每个操作都通过logging模块记录级别分为INFO正常操作、ERROR失败异常。当测试失败时查看日志能快速定位问题发生在哪个步骤。失败截图在find_element失败时自动截图这是调试的“后悔药”。截图文件名包含时间戳避免覆盖并统一存放到reports/screenshots目录方便与报告关联。通用操作封装click,input_text,get_text这些高频操作被标准化子类页面对象直接调用即可代码简洁且统一。3.3 实现第一个页面对象LoginPage现在我们用BasePage来构建一个具体的登录页面类。假设我们有一个简单的登录页用户名输入框ID是username密码框ID是password登录按钮ID是submit。# page_objects/login_page.py from selenium.webdriver.common.by import By from common.base_page import BasePage class LoginPage(BasePage): 登录页面对象模型 # 定位器将页面元素定位方式集中管理 USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) SUBMIT_BUTTON (By.ID, submit) ERROR_MSG_SPAN (By.CLASS_NAME, error-message) def __init__(self, driver): super().__init__(driver) # 调用父类初始化方法 self.driver driver # 可以在这里添加页面特有的初始化逻辑比如访问登录页URL # self.driver.get(https://your-app.com/login) def login(self, username, password): 登录操作输入用户名、密码并点击提交 self.logger.info(f执行登录操作用户名: {username}) self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.SUBMIT_BUTTON) # 登录后通常页面会跳转可以返回下一个页面的对象如HomePage # from page_objects.home_page import HomePage # return HomePage(self.driver) def get_error_message(self): 获取登录错误提示信息 if self.is_element_visible(self.ERROR_MSG_SPAN): return self.get_text(self.ERROR_MSG_SPAN) return None def is_login_page_loaded(self): 检查登录页面是否成功加载 return self.is_element_visible(self.USERNAME_INPUT)实操心得定位器管理将所有定位器定义为类变量By.ID, “username”这是POM的核心。如果页面元素变了你只需要修改这里的常量值所有用到它的方法都会自动更新。操作组合login方法将输入用户名、密码、点击登录这三个步骤组合成一个业务操作。测试用例中只需调用page.login(“user”, “pass”)极大提升了可读性。页面跳转处理登录成功后往往进入首页。好的实践是让login方法返回下一个页面的对象如HomePage这样测试用例可以链式调用home_page login_page.login(…); home_page.do_something()流程非常清晰。3.4 编写你的第一个Pytest测试用例有了LoginPage我们现在可以编写一个真正的测试用例了。使用Pytest测试文件以test_开头测试函数也以test_开头。# test_cases/test_login.py import pytest import logging from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from page_objects.login_page import LoginPage class TestLogin: 登录功能测试类 pytest.fixture(scopeclass) def driver(self): Fixture: 为整个测试类启动和关闭浏览器 # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) # 配置浏览器选项例如无头模式、忽略证书错误等 options webdriver.ChromeOptions() options.add_argument(--ignore-certificate-errors) # options.add_argument(--headless) # 无头模式不打开GUI窗口 driver webdriver.Chrome(serviceservice, optionsoptions) driver.maximize_window() # 最大化窗口 driver.implicitly_wait(5) # 隐式等待作为查找元素的全局超时 yield driver # 将driver对象提供给测试用例使用 # 所有测试用例执行完毕后执行清理工作 driver.quit() logging.info(浏览器已关闭。) pytest.fixture def login_page(self, driver): Fixture: 初始化登录页面对象每个测试函数都会重新初始化 # 这里假设登录页的URL实际项目中可以从配置文件中读取 driver.get(https://your-app.com/login) page LoginPage(driver) assert page.is_login_page_loaded(), 登录页面未能成功加载 return page def test_login_success(self, login_page): 测试用例使用正确的用户名和密码登录成功 # 准备测试数据 username correct_user password correct_password # 执行操作 login_page.login(username, password) # 验证断言登录成功后应该跳转到首页首页会有特定的欢迎元素 # 这里以检查URL是否包含“dashboard”为例 assert dashboard in login_page.driver.current_url.lower() logging.info(f登录成功当前URL: {login_page.driver.current_url}) def test_login_failure_with_wrong_password(self, login_page): 测试用例使用错误密码登录应显示错误信息 username correct_user wrong_password wrong_password login_page.login(username, wrong_password) # 验证断言页面应显示错误提示信息 error_msg login_page.get_error_message() assert error_msg is not None, 未找到错误提示信息 assert 密码错误 in error_msg or invalid in error_msg.lower() logging.info(f登录失败错误信息: {error_msg}) pytest.mark.parametrize(username, password, [ (, somepassword), # 用户名为空 (someuser, ), # 密码为空 (, ), # 都为空 ]) def test_login_failure_with_empty_credentials(self, login_page, username, password): 参数化测试测试用户名或密码为空的登录失败场景 login_page.login(username, password) error_msg login_page.get_error_message() assert error_msg is not None # 根据实际应用提示信息调整断言内容 assert 不能为空 in error_msg or required in error_msg.lower()关键点解析Pytest Fixture (pytest.fixture)这是Pytest的灵魂。driver这个fixture的scope”class”表示整个TestLogin类只启动一次浏览器所有测试用例共用最后统一关闭。login_page这个fixture的scope默认是function表示每个测试函数都会重新获取一个新的页面对象保证了测试之间的独立性。yield关键字在fixture中yield之前的代码是“设置”setupyield返回的是提供给测试用例的对象yield之后的代码是“清理”teardown。这种写法非常清晰。隐式等待与显式等待结合在driverfixture中设置了implicitly_wait(5)作为全局兜底。在BasePage中我们主要使用更精确的显式等待。两者结合既保证了效率又增强了健壮性。参数化测试 (pytest.mark.parametrize)这是Pytest的一大亮点。一个测试函数通过装饰器传入多组数据就能自动生成多个测试用例。这极大地减少了重复代码非常适合测试边界值和多种输入组合的场景。断言Assert测试的核心是验证。使用Python标准的assert语句如果条件为False测试就会失败并输出错误信息。断言应该清晰、具体地描述期望的结果。4. 高级功能集成与框架完善基础框架搭建完成后我们可以引入更多提升效率和体验的组件。4.1 数据驱动测试让测试数据与代码分离硬编码的测试数据如username “testuser”不利于维护和扩展。数据驱动测试Data-Driven Testing DDT将测试数据存储在外部文件如JSON、CSV、Excel中测试用例从文件中读取数据执行。这样增加测试场景只需修改数据文件无需改动代码。我们以JSON为例// test_data/login_data.json [ { test_case: login_success, username: standard_user, password: secret_sauce, expected_result: success, expected_url_contains: inventory }, { test_case: login_locked_user, username: locked_out_user, password: secret_sauce, expected_result: failure, expected_error_msg: Epic sadface: Sorry, this user has been locked out. }, { test_case: login_wrong_password, username: standard_user, password: wrong, expected_result: failure, expected_error_msg: Epic sadface: Username and password do not match any user in this service } ]然后在测试用例中读取并使用这些数据# test_cases/test_login_ddt.py import json import pytest class TestLoginDDT: pytest.fixture def load_login_data(self): Fixture: 从JSON文件加载测试数据 with open(./test_data/login_data.json, r, encodingutf-8) as f: data json.load(f) return data pytest.mark.parametrize(data_item, load_login_data.__wrapped__({})) def test_login_with_data(self, login_page, data_item): 使用数据驱动的登录测试 username data_item[username] password data_item[password] expected_result data_item[expected_result] login_page.login(username, password) if expected_result success: assert data_item[expected_url_contains] in login_page.driver.current_url elif expected_result failure: error_msg login_page.get_error_message() assert error_msg is not None assert data_item[expected_error_msg] in error_msg注意这里为了简化演示直接在一个测试函数里处理了成功和失败两种断言逻辑。更复杂的场景可以为成功和失败分别编写测试函数或者使用更灵活的数据结构。4.2 生成Allure测试报告Allure报告能提供无与伦比的测试执行洞察力。首先确保已安装allure-pytest和Allure命令行工具可从官网下载并配置系统PATH。在pytest执行命令中加入Allure相关参数# 在项目根目录下执行 pytest test_cases/ -v --alluredir./reports/allure_raw这条命令会运行test_cases目录下的所有测试并将原始的测试结果数据一堆JSON文件输出到./reports/allure_raw目录。然后使用Allure命令行工具生成可交互的HTML报告allure generate ./reports/allure_raw -o ./reports/allure_html --clean allure open ./reports/allure_htmlallure generate命令将原始数据转换成漂亮的HTML报告allure open会在默认浏览器中打开它。报告里可以看到测试套件树、通过率、耗时、每个测试用例的详细步骤需要在代码中用allure.step装饰器标记、以及我们之前附加的截图和日志信息一目了然。为了让步骤更清晰我们可以在页面对象的方法上添加Allure步骤装饰器# 修改 common/base_page.py 或 page_objects/login_page.py import allure class LoginPage(BasePage): allure.step(登录操作输入用户名 {username} 和密码) def login(self, username, password): self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.SUBMIT_BUTTON)4.3 配置化与日志系统一个成熟的框架需要良好的配置管理和日志记录。配置文件使用config.ini或config.yaml来管理环境变量、URL、超时时间、浏览器类型等。# config.ini [ENVIRONMENT] base_url https://your-app.com browser chrome headless False [TIMEOUT] implicit_wait 5 explicit_wait 10 page_load_timeout 30 [PATH] screenshot_dir ./reports/screenshots log_dir ./logs在框架中用一个Config类来读取这些配置。日志系统使用Python的logging模块配置不同的处理器Handler将日志同时输出到控制台和文件。# common/logger.py import logging import os from datetime import datetime def setup_logger(name, log_levellogging.INFO): 配置并返回一个logger实例 # 创建logger logger logging.getLogger(name) logger.setLevel(log_level) # 避免重复添加handler if logger.handlers: return logger # 定义日志格式 formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s) # 控制台处理器 console_handler logging.StreamHandler() console_handler.setLevel(log_level) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 文件处理器 log_dir ./logs os.makedirs(log_dir, exist_okTrue) log_file os.path.join(log_dir, fautomation_{datetime.now().strftime(%Y%m%d)}.log) file_handler logging.FileHandler(log_file, encodingutf-8) file_handler.setLevel(logging.DEBUG) # 文件里记录更详细的DEBUG信息 file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger在BasePage的__init__方法中使用self.logger setup_logger(__name__)来初始化日志器。5. 常见问题与排查技巧实录在实际搭建和运行过程中你一定会遇到各种各样的问题。这里记录了一些典型问题及其解决方案。5.1 元素定位失败自动化测试的头号敌人问题现象NoSuchElementException,TimeoutException脚本报错说找不到元素。排查思路与解决方案等待问题最常见症状页面还没加载完脚本就去查找元素。解决优先使用WebDriverWait配合expected_conditions如presence_of_element_located,element_to_be_clickable。减少或避免使用time.sleep()。技巧在BasePage的find_element方法中已经集成了显式等待这是第一道防线。定位器问题症状元素ID、Class或XPath变了或者页面上有多个相似元素。解决使用浏览器开发者工具在Elements面板右键元素Copy - Copy selector / Copy XPath。但要注意自动生成的XPath可能很脆弱。优先使用稳定的属性idnameclasstext。id通常是唯一且最稳定的。使用相对XPath或CSS Selector避免使用绝对路径如/html/body/div[7]/div[2]/...它们极易因页面结构微小变动而失效。使用基于属性、文本或层级关系的相对路径。示例//button[contains(class, ‘submit-btn’)]比//*[id“app”]/div/div[2]/button要好得多。元素在iframe或Shadow DOM中症状在普通DOM里死活找不到。解决iframe使用driver.switch_to.frame(frame_reference)切换到iframe内部操作完后再用driver.switch_to.default_content()切回来。Shadow DOMSelenium 4提供了对Shadow DOM的支持可以使用driver.execute_script执行JavaScript来穿透Shadow Root查找元素。页面弹窗或动态内容症状操作后弹出一个提示框挡住了后面的元素。解决在操作后显式等待弹窗出现并处理它如点击确认或者使用driver.switch_to.alert处理JavaScript原生弹窗。实操心得当元素定位失败时不要盲目修改代码。首先在脚本报错的那一行手动打印出当前的页面URL和页面源代码driver.page_source看看元素是否真的在页面上。其次在浏览器开发者工具的Console里尝试用JavaScript验证你的定位器是否有效例如$x(‘你的XPath’)或document.querySelector(‘你的CSS Selector’)。5.2 测试不稳定Flaky Tests问题现象同一个测试用例有时能过有时失败没有规律。排查思路与解决方案异步加载与网络延迟这是不稳定的主因。确保对所有依赖于网络请求或动态渲染的元素都使用了足够的、合适的等待。依赖外部状态测试用例依赖于一个特定的、可能变化的数据状态如数据库里只有一条特定记录。尽量让每个测试用例独立使用测试数据准备和清理机制setUp/tearDown或Pytest的fixture。浏览器窗口/标签页变化某些操作如点击一个链接可能会打开新窗口或标签页。需要使用driver.switch_to.window(driver.window_handles[-1])来切换到新窗口操作完再切回来。使用重试机制对于某些非核心的、已知偶尔会因环境问题失败的检查点可以使用重试装饰器如pytest-rerunfailures插件。5.3 如何组织大量测试用例并选择性地运行当有成百上千个测试用例时你需要对它们进行分类和管理。使用Pytest标记Markers# conftest.py 中注册自定义标记 def pytest_configure(config): config.addinivalue_line( markers, smoke: 冒烟测试用例 ) config.addinivalue_line( markers, regression: 回归测试用例 ) # 在测试用例上打标记 # test_cases/test_login.py import pytest pytest.mark.smoke def test_login_success(self, login_page): pass pytest.mark.regression def test_login_failure(self, login_page): pass然后可以只运行冒烟测试pytest -m smoke按目录或文件名运行pytest test_cases/运行整个目录。pytest test_cases/test_login.py运行单个文件。pytest test_cases/test_login.py::TestLogin::test_login_success运行单个测试函数。使用pytest.ini配置文件可以配置默认的测试路径、命令行参数等。5.4 在CI/CD流水线中集成自动化测试框架的最终价值在于持续集成。你可以将测试框架集成到Jenkins、GitLab CI、GitHub Actions等工具中。核心步骤环境准备在CI服务器上安装Python、项目依赖通过requirements.txt、浏览器如Chrome和对应的驱动可使用webdriver-manager自动处理。执行测试在CI脚本中执行命令如pytest --alluredir./allure-results。通常使用无头模式--headless以节省资源。收集结果将测试结果如allure-results目录、日志、截图作为构建产物保存。生成与展示报告在构建后步骤中调用allure generate生成HTML报告并发布到CI服务器或专门的报告服务器上。一个简单的GitHub Actions工作流示例.github/workflows/test.ymlname: Python Selenium Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install dependencies run: | pip install -r requirements.txt sudo apt-get update sudo apt-get install -y chromium-browser - name: Run tests with pytest run: | pytest -v --alluredir./allure-results - name: Upload Allure report uses: actions/upload-artifactv3 with: name: allure-report path: ./allure-results搭建一个自动化测试框架从最初的几个脚本到如今这个结构清晰、功能完备的体系最大的体会是**“磨刀不误砍柴工”**。前期在架构设计、基础封装上多花一点时间后期在编写新用例、维护旧用例、排查问题时节省的时间是成倍的。这个框架的每个部分——POM、Fixture、数据驱动、日志报告——都不是炫技而是为了解决实际工程中的痛点。当你下次再面对一个需要自动化的新功能时你会发现大部分基础工作已经就绪你只需要专注于编写新的页面对象和业务测试逻辑效率和质量都得到了保障。最后一个小技巧定期回顾和重构你的框架代码随着项目和技术的发展你可能会发现更优雅的实现方式保持框架的活力同样重要。