1 编码前的准备工作与基本指导思想
测试一个网站就是针对该网站测试场景的一次项目开发,所以项目开发中的理念与思想可以借鉴过来。接到测试需求后,不要一开始就陷入按钮、字段、下拉框等页面元素怎么操作的技术细节当中,而要站在最终用户的角度去分析这个测试需求的交互逻辑和依赖关系,从而将其拆解为一个个相对独立的测试用例。而对于每一个测试用例,并不是每一步都必须使用 Selenium 去自动化实现,而是要根据实际情况将关键的部分自动化,其它非关键的部分可以通过植入数据或调用 API 来实现。
拆解完成后,应该如何编排测试代码?下面将探讨这个问题。
2 如何编排测试代码?
如何编排测试代码?即采用何种策略组织与编排测试代码,从而让代码更简洁且更好维护。
我们依然使用实际的例子来说明将要探讨的问题。
下面是一个「Selenium Web 表单示例页面」的自动化测试动图:
可以看到,该动图展示的自动化测试代码对表单页面进行了文本输入、密码输入、下拉框选项选择和日期输入,并点击了提交按钮,最后跳转至已提交页面。
对应动图原始的 Python 测试代码(original_form_test.py)如下:
python
代码解读
复制代码
from unittest import TestCase from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class TestForm(TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.addCleanup(self.driver.quit) def test_web_form(self) -> None: # 打开表单页面 self.driver.get('https://www.selenium.dev/selenium/web/web-form.html') self.assertEqual(self.driver.title, 'Web form') # Text 输入 text_input_elem = self.driver.find_element(By.ID, 'my-text-id') text_input_elem.send_keys('Selenium') # Password 输入 password_elem = self.driver.find_element(By.NAME, 'my-password') password_elem.send_keys('Selenium') # Dropdown 选择 dropdown_elem = Select(self.driver.find_element(By.NAME, 'my-select')) dropdown_elem.select_by_value('2') # 日期输入 date_input_elem = self.driver.find_element(By.XPATH, '//input[@name="my-date"]') date_input_elem.send_keys('05/10/2023') # 点击 Submit 按钮 submit_button_elem = self.driver.find_element(By.XPATH, '//button[@type="submit"]') submit_button_elem.click() # 等待进入已提交页面 WebDriverWait(self.driver, 10).until(EC.title_is('Web form - target page')) # 断言 message = self.driver.find_element(By.ID, 'message').text self.assertEqual(message, 'Received!')
可以看到,这是一种常见的最直接的测试代码编写方式。
然而这种写法存在几个问题:
-
测试代码(
assertEqual
)和定位与操作元素的代码(find_element ... send_keys ... click
)耦合在一起。这样,如果元素定位标识发生变化或者元素操作方式发生变化,这块测试代码都需要修改。 -
如果要编写针对该页面本身或者依赖该页面的其它测试代码,定位与操作元素的代码都需要重写一遍。
要解决如上问题,须从代码结构与代码编排上着手,即引入一种设计模式 ------ 页面对象模型。
页面对象模型(Page Object Model)借鉴了面向对象编程的思想,是一种在自动化测试中被广泛使用的设计模式,用于减少重复代码并增加代码的可维护性。页面对象是一个面向对象的类,其将同一页面的 Web 元素存储在同一个对象中;当需要与该对象的 UI 进行交互时,不直接访问该页面的 Web 元素,而通过调用该对象提供的方法来实现。这样做的好处是如果某个页面的 UI 发生了改变,测试代码无须更改,只需要更改对应页面对象内的代码即可。
总结一下,使用页面对象模型的好处包括:
- 测试代码与特定于页面的代码分离(增加了简洁性);
- 页面元素和功能被封装在页面对象的属性和方法中,而不是让其分散在整个测试代码中(减少了重复代码并增加了可维护性)。
下面就使用页面对象模型设计一下测试项目的目录结构:
shell
代码解读
复制代码
$ tree . ├─ pages │ ├─ form.py │ └─ form_target.py └─ optimized_form_test.py
可以看到,针对各个页面的页面对象被放在pages
目录下,编写测试用例时调用其对应的方法即可。
下面看一下优化后的代码。
Form
页面对象代码(form.py):
python
代码解读
复制代码
from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from pages.form_target import FormTarget from typing import Self class Form: def __init__(self, driver) -> None: self.driver = driver def open(self) -> Self: self.driver.get('https://www.selenium.dev/selenium/web/web-form.html') return self def get_title(self) -> str: return self.driver.title def input_text(self, text: str) -> Self: elem = self.driver.find_element(By.ID, 'my-text-id') elem.send_keys(text) return self def input_password(self, password: str) -> Self: elem = self.driver.find_element(By.NAME, 'my-password') elem.send_keys(password) return self def select_from_dropdown(self, value: str) -> Self: elem = Select(self.driver.find_element(By.NAME, 'my-select')) elem.select_by_value(value) return self def input_date(self, date: str) -> Self: elem = self.driver.find_element(By.XPATH, '//input[@name="my-date"]') elem.send_keys(date) return self def submit(self) -> FormTarget: elem = self.driver.find_element(By.XPATH, '//button[@type="submit"]') elem.click() # 等待进入已提交页面 WebDriverWait(self.driver, 10).until(EC.title_is('Web form - target page')) # 返回 FormTarget 对象 return FormTarget(self.driver)
FormTarget
页面对象代码(form_target.py):
python
代码解读
复制代码
from selenium.webdriver.common.by import By class FormTarget: def __init__(self, driver) -> None: self.driver = driver def get_message_text(self) -> str: return self.driver.find_element(By.ID, 'message').text
使用如上两个页面对象后的测试代码(optimized_form_test.py):
python
代码解读
复制代码
from unittest import TestCase from selenium import webdriver from pages.form import Form class TestForm(TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.addCleanup(self.driver.quit) def test_web_form(self) -> None: # 打开表单页面 form_page = Form(self.driver) form_page.open() self.assertEqual(form_page.get_title(), 'Web form') # 输入 form_target_page = form_page.input_text('Selenium') \ .input_password('Selenium') \ .select_from_dropdown('2') \ .input_date('05/10/2023') \ .submit() # 断言 message = form_target_page.get_message_text() self.assertEqual(message, 'Received!')
可以看到,经过优化后的代码清晰了许多。
下面总结一下使用页面对象模型时的几个注意事项:
- 断言是测试逻辑的一部分,应放在测试代码中,因此,页面对象中不应有断言或验证相关的代码;
- 页面对象只应将页面提供的服务通过公共方法暴露出来,其它内部细节不要暴露出来。
接下来关注一下实现细节,定位器的使用是编写 Selenium 测试代码时大量涉及的工作。下面看一下定位器使用相关的最佳实践。
3 如何根据情况使用适当的定位器?
前文「Selenium WebDriver 基础使用」中介绍了 Selenium 有 8 种基本的元素定位方法。而什么时候使用什么样的定位器呢?下面给出其最佳实践。
id 定位器
为首选定位方法,准确快速;若元素没有id
,则使用css 选择器
;此两种不可用,再选择xpath 定位器
(相对前两种性能较差);一般在页面上会有多个相同 tag 的元素,所以tag 定位器
一般用于选择一组元素。
综上,本文介绍了构建一个大型测试项目时分析需求的基本指导思想、编排测试代码的实践策略以及使用定位器的推荐顺序。本文涉及的所有代码均已上传至本人 GitHub,欢迎关注。期待阅读完本文,我们对 Selenium 自动化测试从需求分析、编码实现到实现细节上都有了一个可以参考的规范。
这是我整理的**《2024最新Python自动化测试全套教程》** ,以及配套的接口文档/项目实战**【网盘资源】** ,需要的朋友可以下方视频的置顶评论获取。肯定会给你带来帮助和方向。
【已更新】B站讲的最详细的Python接口自动化测试实战教程全集(实战最新版)