Selenium WebUI自动化测试框架搭建:从原理到实战的完整指南

Selenium WebUI自动化测试框架搭建:从原理到实战的完整指南 1. 项目概述为什么WebUI自动化测试是研发流程的“守门员”做Web开发或者测试的朋友肯定都经历过这样的场景每次发版前测试同学都要把核心功能从头到尾点一遍日复一日枯燥且容易遗漏开发改了一个看似无关的按钮样式结果导致另一个页面的表单提交挂了。这种重复、低效且依赖人力的验证方式在快速迭代的今天越来越成为瓶颈。而Selenium就是解决这个痛点的利器它让你能用代码模拟真实用户的操作把那些重复的点击、输入、验证流程自动化起来。简单说Selenium是一个用于Web应用程序自动化测试的工具集。它支持多种浏览器Chrome, Firefox, Edge等和多种编程语言Python, Java, C#等。它的核心价值在于能够驱动浏览器像真人一样去操作网页元素从而完成功能回归、兼容性测试等任务。这不仅仅是“解放测试同学的双手”更深层的意义在于它能将测试活动左移成为持续集成/持续交付CI/CD流水线中不可或缺的一环确保每次代码提交都不会破坏已有的核心功能充当了质量保障的自动化守门员。适合阅读这篇内容的人包括刚接触自动化测试的测试工程师、希望提升项目质量保障能力的开发工程师、以及任何对通过编程控制浏览器感兴趣的技术爱好者。即使你之前没写过自动化测试脚本只要对编程有基本了解也能跟着一步步搭建起自己的测试框架。2. 自动化测试框架的整体设计与核心思路当我们谈论Selenium自动化测试时绝不能停留在“写几个脚本点点按钮”的层面。一个可持续、易维护、高效率的自动化测试项目背后必须有一套清晰的框架设计思想。直接上手写find_element和click()很快就会陷入脚本冗余、维护地狱的泥潭。这里我结合多年的实战经验拆解一下一个健壮的WebUI自动化测试框架应该如何设计。2.1 需求整理明确自动化要解决什么问题在动手写第一行代码之前必须先想清楚几个问题。很多团队自动化项目失败根源就在于需求不清为了自动化而自动化。核心需求通常包括回归测试确保新功能上线或代码修改后原有的核心业务链路如用户登录、下单支付依然畅通。这是自动化测试最主要的价值。冒烟测试在每日构建或每次提交后快速执行一组最基础的测试用例验证系统基本功能是否可用如果失败则阻止后续更耗时的测试。跨浏览器兼容性测试验证Web应用在Chrome、Firefox、Safari等不同浏览器及主要版本上的表现是否一致。数据驱动测试用同一套测试逻辑遍历多组不同的输入数据如不同的用户名/密码组合提高测试覆盖率。非功能性需求框架需求则决定了项目的可维护性用例批量执行与管理能够方便地选择、组织、批量运行成百上千个测试用例。测试报告测试结束后需要一份清晰、直观的HTML报告明确展示哪些用例通过、哪些失败以及失败的详细原因和截图。日志记录运行过程中需要详细的日志方便在测试失败时进行问题定位。异常处理与失败重试网络波动、元素加载稍慢可能导致偶发失败框架应具备一定的容错和重试机制。与CI/CD工具集成能够无缝接入Jenkins、GitLab CI等工具实现测试的自动化触发。基于这些需求一个典型的自动化测试框架会选择Python Pytest/Unittest Selenium的技术栈。Python语法简洁生态丰富Pytest比Unittest更灵活强大插件生态好Selenium则是完成浏览器操作的基石。2.2 详细设计与框架分层高内聚、低耦合一个好的框架必须是分层的各司其职。我推荐的是经典的四层结构这与企业级应用开发中的分层思想一脉相承。第一层基础层Base这是框架的基石主要封装Selenium的原生API提供所有页面对象和测试用例都需要用到的基础操作。比如封装一个通用的find_element方法加入显式等待WebDriverWait解决元素加载时序问题。封装常用的操作点击、输入、获取文本、判断元素是否存在等。初始化浏览器驱动WebDriver管理浏览器会话启动、退出。提供截图、日志记录等公共方法。这一层的目标是让上层不再直接调用driver.find_element_by_id这样的原生方法而是调用更稳定、功能更丰富的自定义方法。第二层页面对象层Page Objects这是Page Object ModelPOM设计模式的核心。其核心思想是将每个Web页面抽象成一个类将这个页面上的元素定位器和操作这些元素的方法封装在这个类中。 例如一个登录页LoginPage类会包含元素定位器用户名输入框self.username_input (By.ID, ‘username’)操作方法def input_username(self, name): self.find_element(*self.username_input).send_keys(name)业务组合方法def login(self, username, password):里面依次调用输入用户名、密码和点击登录。这样做的好处极大当页面UI发生变化时比如登录按钮的ID改了你只需要修改这个LoginPage类中的一处定位器所有用到这个登录操作的测试用例都无需改动极大降低了维护成本。第三层测试用例层Test Cases这一层是真正的测试逻辑所在。它调用页面对象层提供的方法组织测试步骤并进行断言Assert。 一个测试用例应该只关注测试逻辑和验证点不关心元素如何定位、浏览器如何操作。例如def test_login_success(self): login_page LoginPage(self.driver) login_page.login(“valid_user”, “valid_pass”) home_page HomePage(self.driver) assert home_page.get_welcome_text() “Welcome, valid_user!”用例层应该保持干净、可读性强像一段自然语言描述的测试场景。第四层数据与配置层Data Config测试数据将测试用例需要用到的数据如用户名、密码、商品ID从代码中分离出来存放在JSON、YAML或Excel文件中。测试用例运行时读取这些数据实现数据驱动。配置文件将浏览器类型、基础URL、超时时间、日志级别等配置信息放在单独的配置文件如config.ini或config.py中。这样在不同环境测试、预生产运行测试时只需修改配置文件无需改动代码。第五层执行与报告层Runner Report测试执行器使用Pytest或Unittest的Test Runner来发现、组织、运行所有测试用例。可以方便地按模块、按标签mark来运行特定测试集。测试报告集成如pytest-html、Allure等报告插件在测试执行后生成美观的HTML报告。报告中应包含用例执行状态、耗时、失败错误栈和失败时的页面截图这是问题排查的关键依据。注意分层不是教条但却是保证项目长期健康发展的关键。初期可能觉得繁琐但当你的用例数超过100个时你会感谢当初做了这个设计。一个常见的反模式是把所有代码定位、操作、断言、数据都堆在一个脚本文件里这种脚本的寿命通常不会超过三个月。3. Selenium核心细节解析与避坑指南理解了框架设计我们深入到Selenium本身。很多人觉得Selenium就是find_element和click但真正决定脚本稳定性和效率的恰恰是那些容易被忽略的细节。3.1 元素定位稳定性的基石元素定位是自动化测试的“第一步”也是失败最多的一步。Selenium提供了8种主要的定位方式By.ID, By.NAME, By.CLASS_NAME, By.TAG_NAME, By.LINK_TEXT, By.PARTIAL_LINK_TEXT, By.CSS_SELECTOR, By.XPATH。如何选择优先级建议如下ID唯一且通常不变是最优选择。但前端框架如Vue, React自动生成的ID可能每次运行都变化需注意。Name对于表单元素Name属性也通常比较稳定。CSS Selector语法简洁解析速度快浏览器原生支持。适合通过class、属性等组合定位。例如input[type‘submit’]。XPath功能最强大可以遍历XML/HTML文档的任何节点。但性能稍差且过于复杂的XPath表达式可读性和稳定性都较差。尽量避免使用绝对路径以/开头因为它对页面结构变化极其敏感。多使用相对路径和属性结合如//button[id‘submit’]。一个关键技巧使用组合定位提高精度。当单一属性无法唯一定位时可以组合使用。例如一个页面上有多个button class‘btn’但其中一个包含了文字“提交”。你可以用XPath//button[class‘btn’ and text()‘提交’]或者CSS Selector需要更多技巧。优先考虑与开发约定为关键测试元素添加稳定的id或>from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待一个元素可见并可点击 element WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, “myButton”)) ) element.click()常用条件包括presence_of_element_located元素存在于DOM、visibility_of_element_located元素可见、element_to_be_clickable元素可点击、text_to_be_present_in_element元素包含特定文本等。实操心得在我的项目中我会在基础层封装一个wait_for_element方法内部使用显式等待并设置合理的超时时间如10-15秒。所有页面对象的元素查找都通过这个方法从而从根本上解决了因加载时序导致的随机失败问题。3.3 浏览器驱动管理与多浏览器支持Selenium需要通过一个“驱动程序”如chromedriver, geckodriver来与真实浏览器通信。管理这些驱动是另一个容易踩坑的地方。驱动下载与匹配必须确保浏览器驱动版本与本地安装的浏览器版本兼容。通常驱动版本号应与浏览器主版本号一致。最好通过像webdriver-manager这样的Python库来管理它可以自动下载、匹配并管理驱动路径省去手动配置的麻烦。pip install webdriver-managerfrom selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager # Chrome driver webdriver.Chrome(ChromeDriverManager().install()) # Firefox driver webdriver.Firefox(executable_pathGeckoDriverManager().install())多浏览器测试为了支持跨浏览器测试框架应该能够方便地切换浏览器。这可以通过配置文件指定浏览器类型在基础层的驱动初始化部分根据配置来创建不同的WebDriver实例。同时需要考虑不同浏览器之间的一些行为差异比如文件上传、弹窗处理等可能需要做适配。3.4 高级交互与特殊场景处理处理弹窗/Alert使用driver.switch_to.alert来获取alert对象然后进行接受accept()或取消dismiss()操作。切换iframe如果元素位于iframe内部必须先切换到对应的iframedriver.switch_to.frame(‘frame_name_or_id’)操作完成后再切回主文档driver.switch_to.default_content()。文件上传对于input type‘file’元素不要尝试去点击“浏览”按钮弹出系统窗口而是直接使用send_keys(‘文件绝对路径’)方法将文件路径发送到该input元素。执行JavaScript有些复杂操作如滚动到元素位置、修改元素属性可能需要借助JavaScriptdriver.execute_script(“arguments[0].scrollIntoView();”, element)。Cookie管理在测试需要登录状态的场景时有时可以先通过API或其他方式获取登录态Cookie然后通过driver.add_cookie({‘name’: ‘xxx’, ‘value’: ‘yyy’})直接添加到浏览器绕过UI登录流程可以极大提升测试速度。4. 基于Pytest的自动化测试框架实操搭建理论说再多不如动手搭一个。下面我将以一个经典的“Python Pytest Selenium Page Object Allure”技术栈为例带你一步步搭建一个可用的自动化测试框架。这个框架具备了前面提到的所有分层思想。4.1 项目目录结构规划清晰的目录结构是框架可维护性的第一步。建议如下your_project/ ├── configs/ # 配置文件目录 │ ├── config.ini # 主配置文件 │ └── test_data.yaml # 测试数据文件 ├── logs/ # 日志文件目录运行时生成 ├── reports/ # 测试报告目录运行时生成 ├── screenshots/ # 失败截图目录运行时生成 ├── common/ # 基础层/公共层 │ ├── __init__.py │ ├── base_page.py # 页面基类封装公共方法 │ ├── base_test.py # 测试用例基类 │ ├── webdriver_factory.py # 浏览器驱动工厂 │ └── logger.py # 日志记录模块 ├── pages/ # 页面对象层 │ ├── __init__.py │ ├── login_page.py # 登录页面 │ └── home_page.py # 主页 ├── test_cases/ # 测试用例层 │ ├── __init__.py │ ├── conftest.py # Pytest共享fixture │ └── test_login.py # 登录测试用例 ├── utils/ # 工具层 │ ├── __init__.py │ ├── file_reader.py # 文件读取工具 │ └── send_email.py # 邮件发送工具可选 ├── requirements.txt # Python依赖包列表 └── pytest.ini # Pytest配置文件4.2 核心模块代码实现1. 配置文件 (configs/config.ini)[Browser] name chrome headless False # 是否无头模式运行适合CI环境 implicit_wait 0 # 建议设为0完全使用显式等待 [Server] base_url https://your-test-site.com [Timeout] element_wait 10 # 元素显式等待超时时间秒 page_load 30 # 页面加载超时时间秒 [Report] title WebUI自动化测试报告2. 浏览器驱动工厂 (common/webdriver_factory.py)from selenium import webdriver from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.firefox.options import Options as FirefoxOptions from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager import configparser import os class WebDriverFactory: def __init__(self): config configparser.ConfigParser() config.read(os.path.join(os.path.dirname(__file__), ‘..’, ‘configs’, ‘config.ini’)) self.browser_name config.get(‘Browser’, ‘name’).lower() self.headless config.getboolean(‘Browser’, ‘headless’) self.base_url config.get(‘Server’, ‘base_url’) def create_driver(self): driver None if self.browser_name “chrome”: options ChromeOptions() if self.headless: options.add_argument(‘--headless’) options.add_argument(‘--no-sandbox’) options.add_argument(‘--disable-dev-shm-usage’) options.add_argument(‘--start-maximized’) # 使用webdriver-manager自动管理驱动 driver webdriver.Chrome(executable_pathChromeDriverManager().install(), optionsoptions) elif self.browser_name “firefox”: options FirefoxOptions() if self.headless: options.add_argument(‘--headless’) driver webdriver.Firefox(executable_pathGeckoDriverManager().install(), optionsoptions) else: raise ValueError(f“Unsupported browser: {self.browser_name}”) driver.implicitly_wait(0) # 禁用隐式等待 driver.set_page_load_timeout(30) driver.get(self.base_url) return driver3. 页面基类 (common/base_page.py)这是框架最核心的封装之一。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException import logging import os from datetime import datetime class BasePage: def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.timeout 10 # 从配置读取更好 def find_element(self, by, locator, timeoutNone): “”“查找单个元素加入显式等待”“” timeout timeout or self.timeout try: element WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located((by, locator)) ) return element except TimeoutException: self.logger.error(f“元素定位超时: {by}{locator}”) self._take_screenshot(“element_not_found”) raise def find_elements(self, by, locator, timeoutNone): “”“查找多个元素”“” timeout timeout or self.timeout try: elements WebDriverWait(self.driver, timeout).until( EC.presence_of_all_elements_located((by, locator)) ) return elements except TimeoutException: self.logger.warning(f“未找到任何元素: {by}{locator}”) return [] # 返回空列表而不是抛出异常有时是预期行为 def click(self, by, locator): “”“点击元素等待其可点击”“” element WebDriverWait(self.driver, self.timeout).until( EC.element_to_be_clickable((by, locator)) ) element.click() self.logger.info(f“点击元素: {locator}”) def input_text(self, by, locator, text): “”“输入文本先清空再输入”“” element self.find_element(by, locator) element.clear() element.send_keys(text) self.logger.info(f“在元素 {locator} 中输入文本: {text}”) def get_text(self, by, locator): “”“获取元素文本”“” element self.find_element(by, locator) return element.text def _take_screenshot(self, name): “”“截图并保存”“” screenshot_dir os.path.join(os.path.dirname(__file__), ‘..’, ‘..’, ‘screenshots’) os.makedirs(screenshot_dir, exist_okTrue) timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) filepath os.path.join(screenshot_dir, f“{name}_{timestamp}.png”) self.driver.save_screenshot(filepath) self.logger.info(f“截图已保存至: {filepath}”) return filepath4. 页面对象示例 (pages/login_page.py)from common.base_page import BasePage from selenium.webdriver.common.by import By class LoginPage(BasePage): # 元素定位器 USERNAME_INPUT (By.ID, ‘username’) PASSWORD_INPUT (By.ID, ‘password’) LOGIN_BUTTON (By.ID, ‘loginBtn’) ERROR_MSG (By.CLASS_NAME, ‘error-message’) def __init__(self, driver): super().__init__(driver) def input_username(self, username): self.input_text(*self.USERNAME_INPUT, username) def input_password(self, password): self.input_text(*self.PASSWORD_INPUT, password) def click_login(self): self.click(*self.LOGIN_BUTTON) def login(self, username, password): “”“业务组合方法执行登录流程”“” self.logger.info(f“尝试登录用户名: {username}”) self.input_username(username) self.input_password(password) self.click_login() def get_error_message(self): “”“获取登录错误提示信息”“” try: return self.get_text(*self.ERROR_MSG) except: return None # 如果没有错误信息元素返回None5. Pytest Fixture与测试用例 (test_cases/conftest.py和test_cases/test_login.py)conftest.py用于定义Pytest的fixture这些fixture可以被同一目录及子目录下的所有测试文件共享。# test_cases/conftest.py import pytest from common.webdriver_factory import WebDriverFactory pytest.fixture(scope“function”) # 每个测试函数执行一次 def driver(): “““提供WebDriver实例”“” factory WebDriverFactory() driver factory.create_driver() yield driver # 测试函数执行时使用这个driver driver.quit() # 测试函数执行完毕后退出浏览器 pytest.fixture def login_page(driver): “““提供登录页面实例”“” from pages.login_page import LoginPage return LoginPage(driver) pytest.fixture def home_page(driver): “““提供主页实例”“” from pages.home_page import HomePage return HomePage(driver)# test_cases/test_login.py import pytest import allure from utils.file_reader import read_test_data # 假设有一个读取YAML数据的工具 allure.feature(“登录功能”) class TestLogin: allure.story(“成功登录”) allure.title(“使用有效凭据登录系统”) def test_login_success(self, driver, login_page, home_page): “”“测试正常登录”“” # 从数据文件读取测试数据 test_data read_test_data(‘login_success’) login_page.login(test_data[‘username’], test_data[‘password’]) # 断言登录后应跳转到主页并显示欢迎信息 welcome_text home_page.get_welcome_text() assert test_data[‘expected_welcome_text’] in welcome_text # 可以附加截图到Allure报告 allure.attach(driver.get_screenshot_as_png(), name“登录成功主页”, attachment_typeallure.attachment_type.PNG) allure.story(“登录失败”) allure.title(“使用错误密码登录应提示错误”) pytest.mark.parametrize(“username, password, expected_error”, [ (“admin”, “wrong”, “密码错误”), (“”, “123456”, “用户名不能为空”), ]) def test_login_failure(self, login_page, username, password, expected_error): “”“参数化测试登录失败场景”“” login_page.login(username, password) actual_error login_page.get_error_message() assert actual_error is not None assert expected_error in actual_error6. 测试执行与报告生成安装依赖pip install pytest selenium webdriver-manager allure-pytest在项目根目录下创建pytest.ini配置文件[pytest] addopts -v -s --alluredir./reports/allure-results testpaths test_cases python_files test_*.py python_classes Test* python_functions test_*执行测试在终端运行pytest。 生成Allure报告首先需要安装Allure命令行工具然后执行allure serve ./reports/allure-results来查看精美的交互式测试报告。5. 常见问题排查与实战经验实录即使框架搭建得再完美在实际运行中还是会遇到各种“坑”。下面是我在多年实践中总结的一些典型问题及其解决方案希望能帮你少走弯路。5.1 元素定位失败最常见也最头疼问题现象NoSuchElementException,ElementNotInteractableException,StaleElementReferenceException。排查思路与解决确认定位器是否正确首先在浏览器的开发者工具F12中使用Console通过$x(‘你的XPath’)或$$(‘你的CSS Selector’)验证定位器是否能找到元素。注意浏览器中看到的HTML结构可能与脚本运行时略有不同特别是动态生成的内容。检查等待是否充分这是最常见的原因。确保使用了WebDriverWait配合合适的条件如element_to_be_clickable。对于动态加载的内容如Ajax可能需要等待特定元素出现或某个条件成立。元素在iframe或shadow DOM中如果元素在iframe里必须先driver.switch_to.frame()切换进去。对于Shadow DOM需要使用driver.execute_script来穿透查找。元素被遮挡如果报ElementNotInteractableException可能是元素被其他层如弹窗、固定导航栏遮挡。可以尝试用driver.execute_script(“arguments[0].scrollIntoView(true);”, element)将元素滚动到视图中或者用ActionChains进行更复杂的鼠标操作。页面结构已变化StaleElementReference你之前找到的元素引用因为页面刷新或DOM更新而“过期”了。解决方案是重新查找该元素。在Page Object的方法内部应该每次操作时都重新查找而不是在__init__中存储一个元素对象。5.2 测试执行速度慢优化策略启用无头模式Headless在CI环境或不需要观察UI时使用--headless参数运行浏览器可以节省大量渲染时间。禁用图片加载对于不关心图片的测试可以通过浏览器选项禁用图片加载加速页面渲染。chrome_options webdriver.ChromeOptions() prefs {“profile.managed_default_content_settings.images”: 2} chrome_options.add_experimental_option(“prefs”, prefs)使用更快的定位器CSS Selector通常比XPath解析更快。减少不必要的等待杜绝time.sleep精确使用显式等待并设置合理的超时时间。并行测试使用pytest-xdist插件可以并行运行多个测试用例充分利用多核CPU。注意处理好测试之间的隔离如使用独立的用户会话或测试数据。5.3 测试在CI服务器上不稳定问题脚本在本地运行良好一到Jenkins或GitLab Runner上就随机失败。可能原因与解决资源不足CI服务器可能内存或CPU不足。确保为Selenium测试分配足够的资源或者考虑使用Selenium Grid将测试分发到更强大的节点。时区与语言环境CI服务器的系统时区或语言设置可能与本地不同影响某些基于时间或本地化文本的断言。在启动浏览器时可以强制指定语言和时区参数。网络延迟差异CI服务器到被测系统的网络可能比本地慢。适当增加显式等待的超时时间。浏览器版本不一致确保CI服务器上的浏览器及驱动版本与本地一致。使用webdriver-manager可以很好地解决这个问题。无图形界面Headless下的差异有些JavaScript或CSS在无头模式下的行为可能与有界面模式不同。如果问题只在Headless下出现可以尝试先在有界面模式下调试或者查找已知的Headless模式兼容性问题。5.4 测试数据管理难题场景测试需要预置数据如注册一个用户测试后需要清理数据避免影响下次测试。最佳实践接口准备数据在pytest.fixture(scope‘function’)中通过调用后端API来创建测试所需的唯一数据如一个随机生成的用户。测试结束后在fixture的清理阶段或另一个fixture中再通过API删除数据。这样比通过UI操作准备数据快得多也更可靠。使用测试数据库隔离为自动化测试准备一个独立的数据库或 schema。每次测试套件开始前从备份恢复数据库快照或者使用事务在每个测试用例结束后回滚。数据工厂Factory使用像factory_boy这样的库可以方便地生成符合业务规则的随机测试数据。5.5 如何设计稳定且有价值的测试用例不是所有功能都适合做UI自动化。UI自动化测试成本高、运行慢、维护代价大。适合自动化的场景高ROI核心业务流程如用户从注册、登录、浏览商品、下单到支付的完整主线流程。关键路径冒烟测试每次构建后必须验证的“生命线”功能。跨浏览器兼容性检查。数据一致性验证在UI上操作后验证数据库或后端状态是否正确。不适合或不优先自动化的场景频繁变化的UI如果页面布局每周都变维护定位器的成本会高到无法接受。验证视觉效果如图片颜色、字体大小、像素级对齐。这类测试更适合视觉回归测试工具如Applitools, Percy。探索性测试需要人类直觉和创造力的测试。编写测试用例的原则原子性一个测试用例只验证一个功能点或场景。失败时能快速定位问题。独立性用例之间不应该有依赖可以以任意顺序单独运行。通过setup和teardown或pytest fixture来保证每个用例开始前环境是干净的。可读性用例名和方法名应该清晰地表达测试意图。使用allure.story和allure.title来增强报告的可读性。稳定性使用可靠的定位器和充分的等待处理常见的异步加载和弹窗。最后记住UI自动化测试是手段不是目的。它的目标是提升研发效率和质量信心而不是追求100%的自动化覆盖率。将它与单元测试、API接口测试结合起来共同构成一个坚实的分层测试体系才是可持续的质量保障之道。在实际项目中我通常会建议将UI自动化的覆盖率控制在核心业务的20%-40%而将更多的精力投入到更稳定、更快速的单元和接口测试中。