一、单元测试示例详解
代码目标:测试计算器的加法函数 add(a, b)
。
# calculator.py
def add(a, b):
return a + b
# test_calculator.py
import pytest
from calculator import add # 导入被测函数
def test_add_normal():
assert add(2, 3) == 5 # 断言:验证2+3是否等于5
def test_add_negative():
assert add(-1, -1) == -2 # 测试负数相加
def test_add_string():
assert add("Hello", "World") == "HelloWorld" # 故意测试字符串拼接(会失败)
关键设计点:
-
为什么用
assert
语句?
assert
是Python的断言关键字,用于验证实际结果是否符合预期。如果断言失败,测试会自动标记为失败。 -
为什么要分开多个测试函数?
每个测试函数对应一个独立场景(如正常值、负数),避免用例间相互影响,提升可读性。 -
为什么第三个测试会失败?
原函数add
设计上是数值加法,但Python的+
运算符对字符串会拼接。这里故意演示“测试用例与需求不符”的场景,提醒开发者注意需求边界。
二、接口测试示例详解
代码目标:测试登录接口是否能正确处理用户名和密码。
# test_api_login.py
import requests
def test_login():
# 1. 定义接口地址和请求数据
url = "http://api.example.com/login"
data = {"username": "test_user", "password": "123456"}
# 2. 发送POST请求
response = requests.post(url, json=data) # 使用json参数自动序列化为JSON
# 3. 验证HTTP状态码是否为200(成功)
assert response.status_code == 200, "接口请求失败"
# 4. 解析返回的JSON数据
result = response.json()
# 5. 验证业务逻辑状态码和返回字段
assert result["code"] == 0, "登录失败" # 假设code=0表示成功
assert "token" in result, "未返回token" # 验证是否返回身份令牌
关键设计点:
-
为什么用
requests.post
?
requests
是Python最常用的HTTP库,post
方法用于发送POST请求,json=data
会自动将字典转换为JSON格式。 -
为什么要检查状态码
200
?
HTTP状态码200表示请求成功。如果返回404(未找到)或500(服务器错误),说明接口本身有问题。 -
为什么要验证
code
和token
?
状态码200只能说明接口可达,实际业务逻辑是否正确需通过返回的JSON数据判断(如code=0
表示成功,token
是登录凭证)。
三、UI测试示例详解(Selenium)
代码目标:用浏览器自动化测试百度搜索功能。
# test_baidu_search.py
from selenium import webdriver
from selenium.webdriver.common.by import By # 定位元素的策略(如ID、XPath)
import time
def test_search():
# 1. 启动浏览器并打开百度
driver = webdriver.Chrome() # 需提前配置ChromeDriver路径
driver.get("https://www.baidu.com")
# 2. 定位搜索框并输入关键词
search_box = driver.find_element(By.ID, "kw") # 通过元素ID定位
search_box.send_keys("自动化测试") # 模拟键盘输入
# 3. 点击“百度一下”按钮
search_button = driver.find_element(By.ID, "su")
search_button.click() # 模拟鼠标点击
# 4. 等待2秒(临时方案,实际应用需优化)
time.sleep(2)
# 5. 验证搜索结果页标题是否包含关键词
assert "自动化测试" in driver.title
# 6. 关闭浏览器
driver.quit()
关键设计点:
-
为什么用
By.ID
定位元素?
元素的ID通常是唯一且稳定的(如百度搜索框的ID是kw
),比XPath更简洁且不易受页面结构变化影响。 -
为什么要用
time.sleep(2)
?
等待页面加载完成。但这是临时方案,更好的做法是用显式等待(如WebDriverWait
),避免因网络延迟导致失败。 -
为什么验证
driver.title
?
搜索结果页的标题通常会包含搜索关键词,这是验证搜索是否成功的简单方式。
四、通用设计原则总结
1. 模块化与复用性
- 封装函数:将重复操作(如登录)封装成函数,减少代码冗余。
- 示例:
def login(driver, username, password): driver.find_element(By.ID, "username").send_keys(username) driver.find_element(By.ID, "password").send_keys(password) driver.find_element(By.ID, "submit").click()
2. 数据驱动测试
- 分离数据与逻辑:用外部文件(如JSON)管理测试数据,支持多场景测试。
- 示例:
import json with open("test_data.json") as f: test_cases = json.load(f)["test_cases"] for case in test_cases: test_login(case["username"], case["password"])
3. 稳定性与容错
- 显式等待:替代
time.sleep
,动态等待元素出现。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "kw")) )
五、常见疑问解答
Q1:为什么单元测试用 pytest
而不是 unittest
?
- 答案:
pytest
更简洁(无需继承类)、支持参数化测试、报告更友好,且兼容unittest
。
Q2:UI测试中如果元素ID变了怎么办?
- 答案:使用
Page Object模式
,将元素定位集中在单独文件中,修改时只需调整一处。
示例:# pages/login_page.py class LoginPage: username_input = (By.ID, "username") password_input = (By.ID, "password") submit_button = (By.ID, "submit")
Q3:接口测试如何模拟异常场景(如错误密码)?
- 答案:设计多组测试数据,验证接口的错误处理逻辑。
示例:# 测试错误密码 data = {"username": "test_user", "password": "wrong"} response = requests.post(url, json=data) assert response.json()["code"] == 1001 # 假设1001表示密码错误