当前位置: 首页 > news >正文

软件测试资源笔记(4万字,持续更新中)

web自动化selenium和unittest篇

selenium部分

基础代码

import time  from selenium import webdriver  
from selenium.webdriver.common.by import By  options = webdriver.EdgeOptions()                 # 创建浏览器配置对象  
options.add_experimental_option("detach", True)     # 设置浏览器与脚本分离  
driver = webdriver.Chrome(options=options)          # 启动浏览器  
# 如果使用谷歌浏览器
# service = Service(r"E:\chromedriver-win64\chromedriver.exe")  
# driver = webdriver.Chrome(service=service)driver.get(r"C:\Users\86189\PycharmProjects\PythonProject\a.html")  driver.find_element(By.XPATH,"//input[@type='text']").send_keys("warren")  
driver.find_element(By.XPATH,"//input[@type='password']").send_keys("1111")  driver.find_element(By.XPATH,"//button[text()='登录']").click()  # 打开百度  
time.sleep(4)  driver.quit()

driver.find_element()

  • 用于查找网页上的一个元素,返回一个 WebElement 对象

  • 需要传入两个参数:

    1. 定位方式By 提供的枚举常量)

    2. 定位值(对应的选择器)

By 定位方式

By 是 Selenium 提供的一个类,封装了各种元素定位方式。常用有:

  • By.ID("id值") → 通过元素的 id 属性定位

  • By.NAME("name值") → 通过 name 属性定位

  • By.CLASS_NAME("class值") → 通过 class 属性定位

  • By.TAG_NAME("tag值") → 通过标签名定位(如 "input", "button")

  • By.LINK_TEXT("文本") → 通过完整文本定位, 比如这种
    ![[Pasted image 20250819093103.png]]

  • By.PARTIAL_LINK_TEXT("部分文本") → 通过链接的部分文本定位

  • By.CSS_SELECTOR("css选择器") → 通过 CSS 选择器定位

  • By.XPATH("xpath表达式") → 通过 XPath 表达式定位(最万能)

.send_keys()

  • 作用:模拟键盘输入

  • 参数:字符串或者特殊键(如回车键 Keys.ENTER

element.send_keys("hello")          # 输入字符串
element.send_keys(Keys.ENTER)       # 模拟回车
element.send_keys("123", Keys.TAB)  # 输入 123 后按下 TAB

XPath定位方式

例如:

<div id="login"><input type="text" name="username" /><input type="password" name="password" /><button>登录</button>
</div>

XPath 的基本语法:

表达式含义
/从根节点开始定位(绝对路径)
//从任意层级查找(相对路径,最常用)
.当前节点
..上一级节点
@属性(attribute)
*通配符,匹配任意节点或属性

常见 XPath 定位方式:

定位方式示例含义
1️⃣ 通过标签名定位//input定位页面上所有 <input> 元素
2️⃣ 通过属性定位//input[@id='username']定位 id 为 “username” 的 input 元素
3️⃣ 多属性组合定位//input[@type='text' and @name='username']同时匹配多个属性
4️⃣ 模糊匹配属性//input[contains(@name,'user')]name 属性中包含 “user”
5️⃣ 以某字符串开头//div[starts-with(@class,'login')]class 以 “login” 开头的 div
6️⃣ 精确匹配文本内容//button[text()='登录']按钮文字为“登录”的元素
7️⃣ 模糊匹配文本内容//button[contains(text(),'登')]文本中包含“登”的按钮
8️⃣ 层级定位(父子关系)//div[@id='login']/input[@name='username']找到 login 下的 username 输入框
9️⃣ 索引定位(同级多个相同标签)(//input[@type='text'])[1]定位第一个文本框
🔟 使用通配符匹配任意标签//*[@id='login']id 为 “login” 的任意元素

绝对路径 vs 相对路径:

类型示例特点
绝对路径/html/body/div[1]/input从根节点开始,容易受页面结构变化影响
相对路径//input[@id='username']从任意位置查找,更常用、更稳定

XPath 中的常用函数:

函数用法示例
contains()包含子串//div[contains(@class,'main')]
starts-with()属性以某字符串开头//input[starts-with(@name,'user')]
text()匹配文本内容//a[text()='首页']
last()最后一个节点(//li)[last()]
position()指定位置(//input)[position()=2]
normalize-space()去掉空格匹配文本//span[normalize-space(text())='确定']

相对路径转绝对路径的方法

import os  file_path = os.path.abspath("openChrome.py")  
url = f"file:///{file_path.replace(os.sep, '/')}"  print(url)

CSS Selector 基本语法

假设 HTML:

<div id="loginBox"><input id="userA" type="text" class="input-text" placeholder="用户名"><input id="passwordA" type="password" class="input-text" placeholder="密码"><button class="btn login-btn">登录</button>
</div>

(1) 按 id 定位

driver.find_element(By.CSS_SELECTOR, "#userA").send_keys("admin")
  • #userA → 选择 id 为 userA 的元素

(2) 按 class 定位

driver.find_element(By.CSS_SELECTOR, ".login-btn").click()
  • .login-btn → 选择 class 包含 login-btn 的元素

(3) 按标签+class

driver.find_element(By.CSS_SELECTOR, "button.login-btn").click()
  • button.login-btn → 限制标签为 <button> 且 class 包含 login-btn

(4) 按属性定位

driver.find_element(By.CSS_SELECTOR, "input[placeholder='用户名']").send_keys("admin")
driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys("123456")
  • [属性名='值'] → 匹配属性值

  • 也可以模糊匹配:

input[placeholder*='用']   # 属性值包含“用”
input[type^='pass']       # 属性值以“pass”开头
input[type$='word']       # 属性值以“word”结尾

浏览器操作方法

方法功能描述
maximize_window()最大化浏览器窗口
set_window_size(width, height)设置窗口宽高(像素)
set_window_position(x, y)设置窗口在屏幕的坐标位置
back()浏览器后退(模拟点击后退按钮)
forward()浏览器前进(模拟点击前进按钮)
refresh()刷新页面(模拟 F5 按键)
close()关闭当前窗口
quit()关闭浏览器驱动(结束所有窗口、释放资源)
title获取页面标题(属性,调用无括号,如 driver.title
current_url获取当前页面 URL(属性,调用无括号,如 driver.current_url

元素信息获取方法

方法功能描述
size获取元素尺寸(宽高,属性,调用无括号,如 element.size
text获取元素文本内容(属性,调用无括号,如 element.text
get_attribute("xxx")获取元素指定属性值(xxx 为属性名,如 get_attribute("id")
is_displayed()判断元素是否可见(返回布尔值)
is_enabled()判断元素是否可用(如按钮是否可点击,返回布尔值)
is_selected()判断元素是否被选中(复选框、单选框专用,返回布尔值 )

frame/iframe 标签操作

  • iframe 是一个嵌套的 HTML 文档

  • Selenium 默认操作的是 最外层 HTML

  • 如果元素在 iframe 里,直接用 find_element / find_elements 是找不到的

#进入框架 
# 方式1:通过 id 或 name
driver.switch_to.frame("frame_id_or_name")# 方式2:通过 WebElement 对象
iframe_element = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe_element)# 出框架  
driver.switch_to.default_content()

下拉框操作

Select 类用于操作 select 标签

ele = driver.find_element(By.XPATH,"//select[@name='cat_id']")
sel = Select(ele)sel.select_by_index(2)sel.select_by_value("2")sel.select_by_visible_text("手机类型")
#实例化对象:
select = Select (element)  
element: <select>标签对应的元素,通过元素定位方式获取,  
例如:driver.find_element_by_id ("selectA")

方法:

  1. select_by_index (index) --> 根据 option 索引来定位,从 0 开始
  2. select_by_value (value) --> 根据 option 属性 value 值来定位
  3. select_by_visible_text (text) --> 根据 option 显示文本来定位

Cookie 操作

方法说明示例
driver.get_cookies()获取当前页面所有 Cookie,返回列表cookies = driver.get_cookies()
driver.get_cookie(name)获取指定名字的 Cookiecookie = driver.get_cookie("PHPSESSID")
driver.add_cookie(cookie_dict)添加 Cookie 到浏览器driver.add_cookie({"name":"PHPSESSID","value":"xxxx"})
driver.delete_cookie(name)删除指定 Cookiedriver.delete_cookie("PHPSESSID")
driver.delete_all_cookies()删除所有 Cookiedriver.delete_all_cookies()

link_textpartial_link_text 的区别

方法含义匹配方式
link_text按超链接的完整文本定位完全匹配,文本必须完全一样
partial_link_text按超链接文本的部分定位模糊匹配,只要包含指定子串即可

都只能用于 <a> 标签(超链接)

常用鼠标操作

使用 ActionChains 后必须调用 .perform() 执行

# 导包
from selenium.webdriver import ActionChains
from selenium import webdriver
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome()
driver.get("https://www.baidu.com")element = driver.find_element(By.ID, "kw")  # 搜索框
#注册
actions = ActionChains(driver)
方法作用示例
click()单击元素actions.click(element).perform()
double_click()双击元素actions.double_click(element).perform()
context_click()右击元素actions.context_click(element).perform()
move_to_element()鼠标悬停到元素actions.move_to_element(element).perform()
click_and_hold()鼠标按下不放actions.click_and_hold(element).perform()
release()鼠标释放actions.release(element).perform()
drag_and_drop(source, target)拖拽元素actions.drag_and_drop(source, target).perform()
drag_and_drop_by_offset(source, x, y)按偏移拖拽actions.drag_and_drop_by_offset(source, 100, 0).perform()

键盘操作

from selenium.webdriver.common.keys import Keyselement = driver.find_element(By.ID, "kw")# 输入文字 + 回车
element.send_keys("Selenium", Keys.ENTER)# 组合操作
actions = ActionChains(driver)
actions.key_down(Keys.SHIFT).send_keys("selenium").key_up(Keys.SHIFT).perform()  # 大写输入
描述
Keys.ENTER回车
Keys.TABTab 键
Keys.ESCAPEEsc
Keys.BACKSPACE删除
Keys.CONTROLCtrl
Keys.ALTAlt
Keys.SHIFTShift
Keys.ARROW_UP/DOWN/LEFT/RIGHT上下左右方向键
Keys.DELETE删除键
Keys.COPYCtrl+C(复制)
Keys.PASTECtrl+V(粘贴)

元素等待

隐式等待(全局等待):

from selenium import webdriverdriver = webdriver.Chrome()
driver.implicitly_wait(10)  # 设置全局等待时间 10 秒driver.get("https://www.baidu.com")
driver.find_element("id", "kw").send_keys("ChatGPT")

显式等待:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECdriver = webdriver.Chrome()
driver.get("https://www.baidu.com")# 显式等待:最多等待 10 秒,直到元素出现
search_box = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "kw"))
)
search_box.send_keys("ChatGPT")

显式与隐式区别:

  1. 作用域:隐式为全局元素,显式等待为单个元素有效

  2. 使用方法:隐式等待直接通过驱动对象调用,而显式等待方法封装在 WebDriverWait 类中

  3. 达到最大超时时长后抛出的异常不同:隐式为 NoSuchElementException,显式等待为 TimeoutException

unitTest部分

TestCaseTestSuiteTestLoaderTestRunner—— 是 Python unittest 框架的四大核心组成部分

可以这样理解:

组件作用
TestCase最小的测试单元(一个测试用例)
TestSuite测试集合(可包含多个 TestCase 或 TestSuite)
TestLoader负责从模块或类中批量收集用例,组成 TestSuite
TestRunner负责执行 Suite 并输出结果(控制台、报告等)

TestCase

  • 每一个测试类都继承自 unittest.TestCase

  • 每一个以 test_ 开头的方法都是一个独立测试用例

from selenium import webdriver  
import time  
import unittest  from selenium.webdriver.common.by import By  class TestUnit1(unittest.TestCase):  # 初始化环境  def setUp(self):  # 1、self 就是类的引用/实例  # 2、全局变量的定义:self.变量名  self.driver = webdriver.Edge()  self.driver.maximize_window()  self.url = "https://www.baidu.com/"  self.driver.get(self.url)  time.sleep(3)  # 在百度中搜索信息  # 测试用例的命名: test_    def test_search1(self):  self.driver.find_element(By.ID, "chat-textarea").send_keys("java")  self.driver.find_element(By.ID,"chat-submit-button").click()  time.sleep(6)  def test_search2(self):  self.driver.find_element(By.ID, "chat-textarea").send_keys("python")  self.driver.find_element(By.ID,"chat-submit-button").click()  time.sleep(6)  # 关闭浏览器  def tearDowm(self):  self.driver.quit()  # 一个入口  if __name__ == "__main__":  unittest.main()

TestSuite —— 测试集合

  • 用来组织多个测试用例测试类

  • 也可以嵌套多个 TestSuite 形成分层结构

示例:

  1. 实例化: suite = unittest.TestSuite ()

  2. 添加用例:suite.addTest (ClassName ("MethodName"))
    (ClassName:为类名;MethodName:为方法名)

  3. 添加扩展:suite.addTest (unittest.makeSuite (ClassName))
    (一次性把某个类里所有以 test_ 开头的方法都加进来)

import unittest  class TestMath(unittest.TestCase):  def test_add(self):  print("运行 test_add")  self.assertEqual(1 + 1, 2)  def test_sub(self):  print("运行 test_sub")  self.assertEqual(5 - 3, 2)import unittest  from demo import TestMath  if __name__ == "__main__":  # 创建一个 TestSuite    suite = unittest.TestSuite()  # 添加单个测试方法  suite.addTest(TestMath("test_add"))  # 添加多个测试方法  suite.addTests([TestMath("test_sub")])  # 创建一个 runner(运行器)来执行 suite    runner = unittest.TextTestRunner()  runner.run(suite)

TestRunner —— 测试运行器

  • 负责执行测试用例,并输出结果

  • 默认运行器:unittest.TextTestRunner()(打印在终端)

  • 也可以用第三方运行器生成报告,比如 HTMLTestRunner

示例:

runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)

输出:

test_add (__main__.TestMath) ... ok
test_sub (__main__.TestMath) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000sOK

TestLoader

从 测试类、测试模块(文件)、 目录中加载 test_ 开头的用例,然后放到 TestSuite

就像一个“收集器”, 把符合条件的测试方法收集起来,交给 TestSuite 组织,最后由 TextTestRunner 执行

方法作用
loadTestsFromTestCase(TestCaseClass)从一个测试类里加载所有 test_ 开头的方法
loadTestsFromModule(module)从一个模块(文件)里加载所有测试用例
loadTestsFromName(name, module=None)按名字加载某个测试用例或类
loadTestsFromNames(names, module=None)按名字批量加载多个测试用例或类
discover(start_dir, pattern="test*.py")在目录下自动搜索所有符合命名规则的测试文件
import unittestclass TestMath(unittest.TestCase):def test_add(self):print("运行 test_add")self.assertEqual(1+1, 2)def test_sub(self):print("运行 test_sub")self.assertEqual(5-3, 2)if __name__ == "__main__":loader = unittest.TestLoader()suite = loader.loadTestsFromTestCase(TestMath)  # 加载 TestMath 里的所有 test_ 方法runner = unittest.TextTestRunner(verbosity=2)runner.run(suite)

组合使用:

import unittestclass TestMath(unittest.TestCase):def test_add(self):self.assertEqual(1 + 2, 3)def test_sub(self):self.assertEqual(5 - 2, 3)class TestString(unittest.TestCase):def test_upper(self):self.assertEqual("hello".upper(), "HELLO")def test_isupper(self):self.assertTrue("HELLO".isupper())def suite():suite = unittest.TestSuite()loader = unittest.TestLoader()suite.addTests(loader.loadTestsFromTestCase(TestMath))suite.addTests(loader.loadTestsFromTestCase(TestString))return suiteif __name__ == "__main__":runner = unittest.TextTestRunner(verbosity=2)runner.run(suite())

断言

序号断言方法断言描述
1assertTrue(expr, msg=None)验证exprtrue,如果为false,则fail
2assertFalse(expr, msg=None)验证exprfalse,如果为true,则fail
3assertEqual(expected, actual, msg=None)验证expected==actual,不等则fail【掌握】
4assertNotEqual(first, second, msg=None)验证first != second,相等则fail
5assertIsNone(obj, msg=None)验证objNone,不是则fail
6assertIsNotNone(obj, msg=None)验证obj不是None,是则fail
7assertIn(member, container, msg=None)验证container 里是否包含member
8assertNotIn(member, container, msg=None)验证是否member not in container

参数化

动态传入多组测试数据, 让一个测试方法执行多次, 可以提高测试用例的复用性

安装依赖:

pip install parameterized

@parameterized.expand() : 为测试方法传入多组参数

import unittest
from parameterized import parameterizeddef add(a, b):return a + bclass TestAdd(unittest.TestCase):# 多组测试数据:(输入a, 输入b, 预期结果)test_data = [(1, 2, 3),    (0, 0, 0),   (-1, 1, 0),   (2.5, 3.5, 6) ]# 用parameterized.expand装饰测试方法,传入测试数据@parameterized.expand(test_data)def test_add(self, a, b, expected):result = add(a, b)self.assertEqual(result, expected)  # 断言结果是否符合预期if __name__ == "__main__":unittest.main()

跳过

用于跳过执行测试函数和测试类

@unittest.skip(reason):无条件跳过

import unittestclass TestExample(unittest.TestCase):@unittest.skip("该功能已废弃,无需测试")  # 无条件跳过def test_old_feature(self):self.assertEqual(1 + 1, 2)  # 此用例不会执行

@unittest.skipIf(condition, reason):条件满足时跳过

import unittest
import sysclass TestExample(unittest.TestCase):@unittest.skipIf(sys.platform == "win32", "Windows系统不支持此功能")def test_linux_feature(self):# 仅在非Windows系统执行self.assertTrue("linux" in sys.platform.lower())

PO 模式:

一种设计模式,用于优化 UI 自动化测试代码的结构,提高代码的可维护性、复用性和可读性

组成:

  1. 页面类

    • 每个页面(或功能模块)对应一个类,例如登录页(LoginPage)、首页(HomePage)
    • 类中包含页面的所有元素定位(如按钮、输入框、链接等)
    • 类中封装该页面的所有操作方法(如输入用户名、点击登录、获取提示信息等)
  2. 测试用例

    • 不直接操作页面元素,而是调用页面类的方法来完成测试步骤
    • 专注于测试逻辑(如数据准备、步骤组合、断言验证等)
  3. 基础层

    • 封装通用的操作方法(如元素查找、点击、输入、等待等),所有页面类继承该基础类,避免代码重复

示例:

  1. 基础层(BasePage)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECclass BasePage:def __init__(self, driver):self.driver = driver# 查找元素(显式等待)def find_element(self, locator, timeout=10):return WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))# 点击元素def click_element(self, locator):self.find_element(locator).click()# 输入文本def input_text(self, locator, text):self.find_element(locator).send_keys(text)
  1. 页面类(LoginPage)
from tkinter.tix import Select  from selenium.webdriver.common.by import By  from web.BasePage import BasePage  class loginPage(BasePage):  USERNAME_LOCATE=(By.XPATH,'//*[@id="username"]')  PASSWORD_LOCATE=(By.XPATH,'//*[@id="password"]')  SEX_LOCATE=(By.XPATH,'//*[@id="gender"]')  LIKES_LOCATE=(By.XPATH,'//*[@id="registerForm"]/div/label[1]/input')  REGISTER_LOCATE=(By.XPATH,'//*[@id="registerBtn"]')  """ 输入用户名 """    def enter_username(self,username):  usernameTextArea=self.input_text(self.USERNAME_LOCATE,username)  def enter_password(self,password):  passwordTextArea=self.input_text(self.PASSWORD_LOCATE,password)  def select_sex(self):  sex_element = self.find_element(self.SEX_LOCATE)  sel = Select(sex_element)  sel.select_by_index(2)  def choose_likes(self):  likes=self.click_element(self.LIKES_LOCATE)  def click_register(self):  r=self.click_element(self.REGISTER_LOCATE)
  1. 测试用例(TestLogin)
import os  
import sys  
import unittest  from selenium import webdriver  
from selenium.webdriver.chrome.service import Service  
from selenium.webdriver.support.wait import WebDriverWait  sys.path.append(os.path.dirname(__file__))  
from selenium.webdriver.support import expected_conditions as EC  
from loginPage import *  class TestLogin(unittest.TestCase):  def setUp(self):  # 初始化浏览器  service = Service(r"E:\chromedriver-win64\chromedriver.exe")  self.driver = webdriver.Chrome(service=service)  # 将相对路径转为绝对路径  file_path = os.path.abspath("test.html")  url = f"file:///{file_path.replace(os.sep, '/')}"  self.driver.get(url)  self.driver.maximize_window()  self.login_page = loginPage(self.driver)  def test_login_failure(self):  self.login_page.enter_username("wrong_user")  self.login_page.enter_password("wrong_pwd")  self.login_page.choose_likes()  self.login_page.click_register()  popup = WebDriverWait(self.driver, 5).until(  EC.visibility_of_element_located((By.ID, "popup"))  )  self.assertIn("wrong_user", popup.text)  # 截图  self.driver.save_screenshot("full_page.png")  popup.screenshot("popup.png")  print("注册弹窗内容:", popup.text)  def tearDown(self):  self.driver.quit()  if __name__ == "__main__":  unittest.main()

两个方法:

setUp(self):

🔹 作用:在每个测试用例运行之前执行,用于做“初始化”操作

tearDown(self):

🔹 作用:在每个测试用例执行后自动执行,用于做“清理”工作。

如果有多个测试方法,比如:

def test_login_success(self):...def test_login_failure(self):...

执行顺序是:

步骤执行内容
1setUp()
2test_login_success()
3tearDown()
4setUp()
5test_login_failure()
6tearDown()

unittest 框架中的命名规则:

  • 测试类名必须以 Test 开头, 并且必须继承自 unittest.TestCase

例如:

class TestLogin(unittest.TestCase):   # ✅ 正确...
  • 测试方法必须以 test_ 开头

例如:

def test_login_success(self):   # ✅ 正确...def test_login_failure(self):   # ✅ 正确...
类型识别规则
测试文件文件名以 test 开头,例如 test_login.py
测试类类名以 Test 开头
测试方法方法名以 test_ 开头

一些细节

元素操作的返回值与链式调用问题
element.click().send_keys("1111")

问题在于:

  • element.click()返回值是 None

  • 所以 .send_keys("1111") 等于在 None 上调用,必然报错。

正确写法应该分两步:

element.click()
element.send_keys("1111")

总结

操作返回值是否可链式调用
click()None❌ 不可链式
send_keys()None❌ 不可链式
find_element()WebElement 对象✅ 可链式
WebDriverWait(...).until(...)WebElement 对象✅ 可链式

🧩 正确可链式写法示例:

driver.find_element(By.ID, "username").send_keys("abc")
元素清空输入问题

如果一个输入框已有值:

element.send_keys("新值")

会直接追加在旧值后面。

正确写法:

element.clear()
element.send_keys("新值")

Pytest测试框架篇

Python篇

type 函数

a = 100  
b = 123.45  
c = 'hello, world'  
d = True  
print(type(a))  # <class 'int'>  
print(type(b))  # <class 'float'>  
print(type(c))  # <class 'str'>  
print(type(d))  # <class 'bool'>

输入 input()

变量 = input("提示信息:")

函数返回值类型是字符串

身份运算符:isis not

作用:

判断 两个变量是否引用同一个对象(内存地址是否相同)
不是判断“值是否相等”,而是判断“是不是同一个对象”。

示例:

a = [1, 2, 3]
b = a
c = [1, 2, 3]print(a is b)      # True  → b 指向同一个对象
print(a is c)      # False → 虽然值相等,但不是同一个对象
print(a == c)      # True  → 值相等

== 判断的是值是否相等

成员运算符:innot in

作用:

判断 某个元素是否存在于序列(字符串、列表、元组、字典、集合)中

示例:

nums = [1, 2, 3, 4]
print(2 in nums)        # True
print(5 not in nums)    # Truetext = "hello"
print("h" in text)      # True
print("z" in text)      # Falsedic = {"name": "Tom", "age": 18}
print("name" in dic)    # True  (判断 key 是否存在)
print("Tom" in dic)     # False (不判断 value)
注意
  1. 小整数缓存机制
    Python 会缓存 -5 ~ 256 的整数,所以:
   a = 100b = 100print(a is b)   # True  → 实际上是同一个缓存对象

但:

   a = 1000b = 1000print(a is b)   # False → 超出缓存范围,不同对象

f-string 格式化

语法:

在字符串前加上 f,直接在 {} 中写变量或表达式。

name = "小明"
age = 18
print(f"我叫 {name},今年 {age} 岁。")

输出:

我叫 小明,今年 18 岁。

match-case语句

基本语法:

match 变量:case 值1:执行语句1case 值2:执行语句2case _:默认执行语句(相当于 else

关键点:

  • match 用来匹配一个表达式或变量;

  • case 用来定义匹配的条件;

  • _ 表示通配符,即“其他情况”;

  • 匹配成功后,程序就不会继续往下匹配,不需要写 break

简单数字匹配:

command = input("请输入命令编号(1-3):")match command:case "1":print("启动系统")case "2":print("关闭系统")case "3":print("重启系统")case _:print("未知命令")

匹配多个值:

fruit = "apple"match fruit:case "apple" | "pear" | "peach":print("这是一种水果")case "carrot" | "potato":print("这是一种蔬菜")case _:print("未知食物")

进阶技巧:守卫(guard)条件:

你还可以在 case 后加上条件判断:

x = int(input("输入数字:"))match x:case n if n < 0:print("负数")case n if n == 0:print("零")case n if n > 0:print("正数")

列表(list)总结

列表简介
  • 特点:有序, 支持索引访问, 元素可以重复;支持动态扩展
创建列表
items1 = [35, 12, 99]            # 使用 [] 创建
items2 = ['Python', 'Java']
items3 = [100, 12.3, 'Python', True]
items4 = list(range(1, 10))      # 使用 list() 构造器
items5 = list('hello')
列表运算
# 拼接
[1,2,3] + [4,5]     # [1,2,3,4,5]
# 重复
[1,2] * 3           # [1,2,1,2,1,2]
# 成员判断
3 in [1,2,3]        # True
4 not in [1,2,3]    # True
索引与切片
  • 索引访问
fruits = ['apple','banana','peach']
fruits[0]      # apple
fruits[-1]     # peach
fruits[1] = 'orange'
  • 切片访问
fruits[0:2]    # ['apple','orange']
fruits[:2]     # ['apple','orange']
fruits[::2]    # ['apple','peach']
fruits[-2::-1] # ['orange','apple']
  • 切片修改
fruits[0:2] = ['x','y']  # 修改指定区间的元素
添加元素
lst = [1,2,3]
lst.append(4)        # [1,2,3,4]
lst.insert(1, 9)     # [1,9,2,3,4]
删除元素
lst.remove(9)        # 删除指定值,若不存在会报错
lst.pop()            # 删除最后一个元素
lst.pop(1)           # 删除指定索引元素
del lst[0]           # 删除指定索引元素
lst.clear()          # 清空列表
查询元素
lst.index(2)         # 返回第一个匹配索引
lst.count(2)         # 统计元素出现次数
排序与反转
lst.sort()           # 升序排序
lst.reverse()        # 元素顺序反转
列表遍历
  • 通过索引遍历
for i in range(len(lst)):print(lst[i])
  • 直接遍历元素
for item in lst:print(item)

切片

列表切片的语法为:

list[start:end:stride]
  • start:起始索引,表示从哪个位置开始取元素(包含该位置的元素)。

  • end:结束索引,表示取到哪个位置结束(不包含该位置的元素)。

  • stride:步长,表示索引每次增加的值(可以为负数表示反向取)。

注意:切片不会改变原列表,它返回的是一个新的列表。

正向切片
fruits = ['apple', 'banana', 'cherry', 'date', 'fig', 'grape']# 从索引1取到索引3(不包含3)
print(fruits[1:3])  # ['banana', 'cherry']# 从索引0取到索引5,每次间隔2
print(fruits[0:5:2])  # ['apple', 'cherry', 'fig']# 从索引2取到最后
print(fruits[2:])  # ['cherry', 'date', 'fig', 'grape']# 从开始取到索引3(不包含3)
print(fruits[:3])  # ['apple', 'banana', 'cherry']# 取整个列表
print(fruits[:])  # ['apple', 'banana', 'cherry', 'date', 'fig', 'grape']
切片赋值(修改元素)
fruits = ['apple', 'banana', 'cherry', 'date', 'fig']# 修改索引1到3的元素
fruits[1:4] = ['blueberry', 'cranberry', 'dragonfruit']
print(fruits)  # ['apple', 'blueberry', 'cranberry', 'dragonfruit', 'fig']# 删除部分元素
fruits[1:3] = []
print(fruits)  # ['apple', 'dragonfruit', 'fig']# 插入元素
fruits[1:1] = ['banana', 'cherry']
print(fruits)  # ['apple', 'banana', 'cherry', 'dragonfruit', 'fig']

字符串

s = 'hello'
print(s + ' world')  # 拼接
print(s * 3)         # 重复
print('h' in s)      # True
print('a' > 'A')     # True
索引与切片
  • 索引s[i] 正向从 0 开始,负向从 -1 开始

  • 切片s[start:end:step],返回新字符串,原字符串不变

s = 'abc123'
print(s[0], s[-1])   # a 3
print(s[1:4])         # bc1
print(s[::2])         # ac2
print(s[::-1])        # 321cba
  • 注意:字符串不可变,不能通过索引直接修改字符。
遍历
for i in range(len(s)):print(s[i])for ch in s:print(ch)
常用字符串方法
功能方法示例说明
大小写转换s.upper() / s.lower() / s.title() / s.capitalize()返回新字符串
查找s.find(sub), s.rfind(sub), s.index(sub)返回索引或 -1 / 异常
开头/结尾判断s.startswith('a'), s.endswith('b')返回 True/False
特性判断s.isdigit(), s.isalpha(), s.isalnum()检查数字、字母、字母数字
修剪s.strip(), s.lstrip(), s.rstrip()去掉首尾指定字符
替换s.replace(old, new, count=-1)替换字符串
拆分/合并s.split(sep=None, maxsplit=-1), sep.join(list)拆分成列表或合并列表
格式化'{0} {1}'.format(a,b), f'{a} {b}'格式化字符串
对齐s.center(width, fill), s.ljust(width, fill), s.rjust(width, fill), s.zfill(width)居中、左/右对齐、补零
编码/解码s.encode(encoding), b.decode(encoding)str ↔ bytes

集合

特点: 无序,元素不能重复, 不支持索引运算

注意:空集合必须使用 set(){} 是空字典。

元素要求:必须是可哈希类型(intfloatstrtuple 等),不可变类型;集合、列表等可变类型不能作为元素。

set1 = {1, 2, 3, 3, 3, 2}
print(set1)set2 = {'banana', 'pitaya', 'apple', 'apple', 'banana', 'grape'}
print(set2)set3 = set('hello')
print(set3)set4 = set([1, 2, 2, 3, 3, 3, 2, 1])
print(set4)set5 = {num for num in range(1, 20) if num % 3 == 0 or num % 7 == 0}
print(set5)
set1 = {1, 2, 3, 4, 5, 6, 7}
set2 = {2, 4, 6, 8, 10}# 交集
print(set1 & set2)                      # {2, 4, 6}
print(set1.intersection(set2))          # {2, 4, 6}# 并集
print(set1 | set2)                      # {1, 2, 3, 4, 5, 6, 7, 8, 10}
print(set1.union(set2))                 # {1, 2, 3, 4, 5, 6, 7, 8, 10}# 差集
print(set1 - set2)                      # {1, 3, 5, 7}
print(set1.difference(set2))            # {1, 3, 5, 7}# 对称差
print(set1 ^ set2)                      # {1, 3, 5, 7, 8, 10}
print(set1.symmetric_difference(set2))  # {1, 3, 5, 7, 8, 10}

集合的方法:

set1 = {1, 10, 100}# 添加元素
set1.add(1000)
set1.add(10000)
print(set1)  # {1, 100, 1000, 10, 10000}# 删除元素
set1.discard(10)
if 100 in set1:set1.remove(100)
print(set1)  # {1, 1000, 10000}# 清空元素
set1.clear()
print(set1)  # set()

元组

  • 有序,可以存放多个元素。

  • 元组是不可变类型,一旦创建,元素不可修改、删除或增加

  • 可以包含任意类型的元素(整数、字符串、布尔值等)

    t1 = (35, 12, 98)               # 三元组
    t2 = ('骆昊', 45, True, '四川成都')  # 四元组t3 = 10, 20, 30  # 等价于 (10, 20, 30)print(type(t1))  # 输出:<class 'tuple'>
    
    • 单元素元组需加逗号:t = (5,)

    • 不加逗号的括号不是元组,而是普通类型

  1. 基本操作

    • 类型与长度

      type(t1)   # <class 'tuple'>
      len(t1)    # 元素数量
      
    • 索引运算

      t1[0]      # 第一个元素
      t2[-1]     # 最后一个元素
      
    • 切片运算

      t2[:2]     # 从开头到索引2(不含)
      t2[::3]    # 步长为3
      
  2. 遍历与成员运算

    for elem in t1:print(elem)12 in t1          # True
    'Hao' not in t2   # True
    
  3. 拼接与比较

    • 拼接t3 = t1 + t2,生成新的元组

    • 比较:按元素逐一比较大小和相等性

      t1 == t3       # False
      t1 >= t3       # False
      t1 <= (35, 11, 99)  # False
      

字典

  • 字典是一种无序的可变容器,以**键值对(key-value)**的形式保存数据。

  • 通过键可以快速访问对应的值,非常适合存储关联数据(例如人的信息、商品信息等)。

  • 键必须是不可变类型(如intfloatstrtuple),值可以是任意类型(可变或不可变)。

    # 字面量语法
    person = {'name': '王大锤', 'age': 55, 'height': 168}# 使用 dict 构造器
    person = dict(name='王大锤', age=55, height=168)# 使用 zip 创建字典
    items = dict(zip('ABCDE', range(1,6)))# 字典生成式
    cubes = {x: x**3 for x in range(1,6)}
    
  • 索引运算:通过键访问或修改值

   person['age'] = 25person['tel'] = '13122334455'
  • 成员运算:判断键是否存在
  'name' in person  # True'tel' in person   # False
  • 循环遍历:遍历字典的键,或用 items() 遍历键值对
        for key, value in person.items():print(f'{key}: {value}')

字典方法:

  • 获取值get 方法避免 KeyError
  person.get('age')        # 25person.get('sex', '男')  # '男'
  • 获取键、值、键值对
  person.keys()person.values()person.items()
  • 更新字典update|=
  person.update({'age': 30, 'addr': '成都'})person |= {'age': 30, 'addr': '成都'}
  • 删除元素poppopitemdel
   person.pop('age')    # 返回被删除的值person.popitem()     # 返回被删除的键值对del person['addr']   # 删除指定键person.clear()       # 清空字典

空函数

空函数指的是没有实现功能,只是一个占位的函数
有时候我们在写程序时,函数的具体实现还没想好,但又想先写好结构,这时就需要空函数。

例如:

def my_function():pass
pass 的作用

Python 不允许定义空代码块
比如:

def func():# 什么都不写

这会报错:

IndentationError: expected an indented block

pass 就是用来防止语法错误的占位符

pass 是一个空语句,执行时什么也不做

# 示例1:空函数中使用 pass
def func():pass# 示例2:空类中使用 pass
class Student:pass# 示例3:空循环中使用 pass
for i in range(5):pass# 示例4:空条件中使用 pass
if True:pass

函数参数

默认参数
  • 如果传了参数 → 使用传入的值
  • 如果没传 → 使用默认值
def greet(name="游客"):print("你好,", name)greet()         # 输出:你好, 游客
greet("小明")   # 输出:你好, 小明

注意默认参数要放在普通参数之后

def func(a, b=10):   # ✅ 正确passdef func(b=10, a):   # ❌ 错误pass

可变参数(*args

*args 可以接收 任意数量的位置参数,在函数内部是一个元组

def show_numbers(*args):print(args)show_numbers(1, 2, 3)
# 输出:(1, 2, 3)

关键字参数(**kwargs

**kwargs 可以接收 任意数量的“键=值”形式参数,是一个字典

def show_info(**kwargs):print(kwargs)show_info(name="小明", age=18)
# 输出:{'name': '小明', 'age': 18}

面向对象

构造方法 __init__

构造方法名字固定为 __init__:

class Student:def __init__(self, name, score):self.name = nameself.score = scorebart = Student("Bart", 59)
print(bart.name, bart.score)  # Bart 59

实例方法
  • 作用:普通的方法,需要通过对象调用,操作对象的属性, 第一个参数必须是 self,表示当前对象本身。
class Student:def __init__(self, name, score):self.name = nameself.score = scoredef print_score(self):print(f"{self.name}: {self.score}")bart = Student("Bart", 59)
bart.print_score()  # Bart: 59

类方法
  • 装饰器@classmethod, 第一个参数必须是 cls,表示当前类
class Student:count = 0  # 类属性def __init__(self, name):self.name = nameStudent.count += 1@classmethoddef print_count(cls):print(f"总共有 {cls.count} 个学生")s1 = Student("Bart")
s2 = Student("Lisa")
Student.print_count()  # 总共有 2 个学生

静态方法
  • 作用:既不操作对象属性,也不操作类属性。

  • 装饰器@staticmethod, 可以看作是类里面的普通函数,用类名或对象都能调用

class Math:@staticmethoddef add(x, y):return x + yprint(Math.add(3, 5))  # 8
访问限制

如果要让内部属性不被外部访问,可以在属性的名称前加两个下划线__,就变成了一个私有变量

class Man:  def __init__(self,name,age):  self.__name=name  self.__age=age  def printInfo(self):  print(self.__name+str(self.__age))  def getName(self):  return self.__name  def getAge(self):  return self.__age

判断一个变量是否是某个类型可以用isinstance()

isinstance(a, list)
isinstance(1, int)
继承
class Parent:def __init__(self, name):self.name = namedef greet(self):print(f"Hello, I am {self.name}")# 子类继承父类
class Child(Parent):passc = Child("Alice")
c.greet()  # Hello, I am Alice

异常处理

try…except
try:r = 10 / 0
except ZeroDivisionError as e:print('捕获到错误:', e)
  • try:放置可能出错的代码

  • except:捕获指定类型的异常

  • 可以捕获多个异常:

try:r = int('abc')  # ValueError
except ZeroDivisionError:print('除零错误')
except ValueError:print('值错误')

  • else 会在 没有异常发生时执行
try:r = 10 / 2
except ZeroDivisionError:print('除零错误')
else:print('没有错误,结果:', r)

  • finally 无论是否发生异常都会执行,常用于释放资源:
try:f = open('test.txt')data = f.read()
except FileNotFoundError:print('文件不存在')
finally:print('关闭文件')f.close()
raise 抛出异常
  • 用于 主动抛出异常,可以中断当前流程,让调用者处理。
def foo(x):if x == 0:raise ValueError("x 不能为 0")return 10 / xfoo(0)  # 会抛出 ValueError
  • 异常是对象,必须是 Exception 的子类

logging 模块

  • logging 用于 记录日志,比 print 更专业,适合生产环境。
import logginglogging.basicConfig(level=logging.INFO)  # 配置日志等级logging.info("这是普通信息")
logging.warning("这是警告信息")
logging.error("这是错误信息")
  • 日志等级从低到高:DEBUG < INFO < WARNING < ERROR < CRITICAL

  • 可以把日志输出到文件或终端。

将日志输出到文件

import logging  # 创建 logger 对象  
logger = logging.getLogger()  
logger.setLevel(logging.INFO)  # 创建文件处理器  
file_handler = logging.FileHandler('app.log', mode='w', encoding='utf-8')  # 创建控制台处理器  
console_handler = logging.StreamHandler()  # 设置统一格式  
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')  
file_handler.setFormatter(formatter)  
console_handler.setFormatter(formatter)  # 将处理器添加到 loggerlogger.addHandler(file_handler)  
logger.addHandler(console_handler)  # 写日志  
logger.info("这是普通信息")  
logger.warning("这是警告信息")  
logger.error("这是错误信息")
  • 可以配置时间、日志等级、输出格式, 日志可长期保存,用于排查问题

with语句

用于 自动管理资源(比如文件、网络连接、锁等),避免手写繁琐的 try...finally

with open('data.txt', 'r') as f:data = f.read()print(data)

等价于:

f = open('data.txt', 'r')
try:data = f.read()print(data)
finally:f.close()

__name__是什么

在每个 Python 文件中,解释器都会自动定义一个变量:它表示当前模块的名字

__name__
  • 如果这个文件是 被直接运行的脚本,那么:

    __name__ == '__main__'
    
  • 如果这个文件是 被其他模块导入的,那么:

    __name__ == 模块名(文件名,不含 .py)
    

作用:

def main():print("程序的主入口")if __name__ == '__main__':main()

只有在该文件被“直接运行”时才会执行 main()
❌ 如果该文件被“import 导入”,不会执行。

文件操作

打开文件:open()
f = open("test.txt", "r", encoding="utf-8")

参数说明:

参数作用
文件名"test.txt" 要打开的文件名
模式"r" 代表读取模式(read)
编码encoding="utf-8" 解决中文乱码问题

常见的打开模式:

模式含义
'r'只读模式(默认)
'w'只写模式(会覆盖原内容)
'a'追加模式(在原内容后面追加)
'rb'以二进制方式读取(read binary)
'wb'以二进制方式写入(write binary)

读取文件内容
f = open("test.txt", "r", encoding="utf-8")
content = f.read()           # 读取全部内容
print(content)f.seek(0)                    # 将文件指针移动到开头
line1 = f.readline()         # 读取一行
lines = f.readlines()        # 读取所有行,返回列表
f.close()

注意:
使用完文件后必须调用 f.close() 关闭,否则可能导致资源未释放或数据未保存。


写入文件
f = open("output.txt", "w", encoding="utf-8")
f.write("Hello, Python!\n")
f.write("写入第二行")
f.close()

如果文件不存在,"w" 模式会创建一个新文件。
如果文件存在,内容会被清空后写入


推荐写法:with open()(自动关闭文件)
with open("test.txt", "r", encoding="utf-8") as f:for line in f:print(line.strip())

with 块结束后,Python 会自动关闭文件,不需要 f.close(), 推荐用这个

常见面试题

测试面试题

软件测试的过程

  • 需求分析

  • 制定测试计划

  • 设计测试用例, 搭建测试环境

  • 执行测试用例

  • 跟踪缺陷,执行回归测试

  • 汇总测试结果,写出测试报告

测试用例一般包含哪些内容?

一共八个

  • 用例编号:项目_模块_编号
  • 标题:预期结果(测试点)
  • 模块 / 项目:所属项目或模块
  • 优先级:表示用例的重要程度或者影响力 P0 ~ p4 (P0 最高)
  • 前置条件:要执行此条用例,有哪些前置操作
  • 测试步骤:描述操作步骤
  • 测试数据:操作的数据,没有的话可以为空
  • 预期结果:期望达到的结果

测试用例的设计方法

  • 等价类划分法:将输入划分为有效 / 无效等价类(如手机号测试:有效 11 位数字,无效 < 11 位 / 非数字),减少用例数量

  • 边界值分析法:重点测试边界(如微信消息最大长度为 2000 字,测试 1999、2000、2001 字)

  • 场景分析法:模拟用户在真实业务场景下的操作流程,来设计测试用例的方法(比如电商 “下单 - 支付 - 发货” 全流程)

  • 因果图法:根据多条件组合来设计测试用例(比如如登录时 “用户名 + 密码” 的正确 / 错误组合)

  • 错误推测法:基于测试人员的经验、历史缺陷数据、用户使用习惯,推测系统可能出现错误的场景

设计测试用例时要注意什么?

  • 覆盖全面:要覆盖功能需求、非功能需求、边界条件、异常场景等

  • 可执行性:步骤清晰、预期结果要提示明确

  • 可维护性:需求变更时,用例要容易修改

  • 符合业务场景

  • 独立性:每个用例专注于一个场景,不能依赖其他用例的执行结果

  • 简洁性:步骤不能冗余,避免重复

软件测试的原则

  1. 要尽早开始测试, 降低修复的成本, 越早发现 bug 成本越低

  2. 穷尽测试是不可能的, 不可能覆盖所有场景进行测试

  3. 测试依赖于上下文, 不同类型的软件有不同测试方法

  4. 缺陷集群效应: 大多数缺陷往往集中在少数模块里

  5. 测试只能显示缺陷存在,不能保证系统完全正确

测试时候遇到不可复现的bug怎么办?

  • 按照原有步骤重新测试, 如果 bug 没有出现,做好记录
  • 检查 bug 出现时的操作流程和环境配置,判断是不是因为误操作或环境异常导致
  • 可以查看日志来分析
  • 更换不同的测试环境来复现问题,排除环境因素的影响
  • 在后续的测试过程中关注这个bug是否出现

如果出现了bug, 你和开发说,开发说不是bug你怎么解决?

  • 重新核对需求文档或接口文档,看双方是否存在对需求的认知偏差

  • 把测试过程、输入输出、截图、日志等整理出来,拿给开发看

  • 参考需求文档/产品文档,看和需求是否相符

  • 可以组织 开发+测试+产品 一起评审, 确定是不是bug

软件测试的目的是什么?

  • 发现缺陷,保证软件质量

  • 验证软件是否满足需求

  • 降低风险,提高用户体验感

黑盒测试与白盒测试的区别是什么?

黑盒测试:是功能性测试,不了解代码结构的前提下,根据功能设计用例来测试
白盒测试:是结构性测试,知道代码逻辑的前提下,根据代码逻辑设计用例,进行用例覆盖

你不是开发吗,为什么选择走测试方向?

我觉得测试是和开发同样重要的岗位。开发是“创造”,测试是“守护”。 在学习的过程中,我发现我对发现问题、分析问题更感兴趣,而不是写功能。同时我有开发的基础,可以更好的理解代码逻辑、写自动化脚本,提升我测试的效率, 所以我选择测试方向

bug的严重程度和优先级

严重程度: 致命,严重,一般,轻微
优先级: 立即解决,高优先级,正常排队,低优先级

和开发说有 bug,bug没改完, 项目快上线了怎么办

先确认 Bug 严重程度和影响范围,然后把复现步骤、截图和日志整理清楚,及时跟开发和产品沟通,说明风险。
如果影响核心功能,建议延期上线或临时规避,否则记录好, 并在下一版本尽快修复,同时提醒上线团队注意。

提Bug要注意哪些点?

提Bug: 问题描述要清晰、Bug的复现步骤要清晰, 环境信息要完整、定位明确、严重性和优先级评估合理、避免重复

从发现到解决 Bug的流程

在执行用例时发现问题,会先复现确认,排查是不是环境或数据导致的。
确认是 Bug 后,就在缺陷管理平台提交,写清楚环境、重现步骤、预期和实际结果、日志和截图。

然后由开发和产品一起评估严重度和优先级,开发定位问题并修复,修复后提交代码和单元测试。
接着测试人员会验证修复情况,并做相关功能的回归测试。

验证通过后就关闭缺陷,如果是高严重度问题,还会做复盘,总结原因,补充测试用例,防止再次发生。

如何确定是不是bug?

先对照需求文档和测试用例,如果和预期不符,可以认定为 Bug。

看下这个问题是否违反了用户的操作习惯,或者行业的通用规范,如果违反了,就是bug

可以找产品经理或者开发人员沟通确定是否为bug

对于需求本身有歧义的情况,可以组织相关人员开会,确认是不是 Bug

以及如何判断一个Bug是前端还是后端引起的?

看 bug 的表现:

  • 前端问题:页面样式错乱、交互时没有反应、浏览器控制台报错、请求参数错误

  • 后端问题:接口返回 500/502/503,返回数据缺失或错误,响应速度慢,数据结构不对

用浏览器开发者工具看请求有没有发出,响应是否正确
用接口工具比如 postman 调接口,看请求响应是否正确
还可以看前后端代码的日志

你觉得测试最重要的能力是什么(核心竞争力)

我觉得软件测试最重要的能力是发现和分析问题的能力
测试的核心价值就是在产品上线前发现潜在的问题,保证质量。
这需要有敏锐的观察力,能从用户角度去思考,捕捉到系统中的异常点;同时还要有逻辑思维,能够通过日志分析、用例设计等方法定位问题根源
除此之外,还需要良好的沟通能力,把发现的问题准确、清晰地反馈给开发团队,推动问题解决。

没有需求文档,怎么开展测试

可以找相关人员进行沟通,获取需求,比如产品经理和开发人员

可以根据用户的使用习惯和行业规范来总结需求

可以参考类似的产品来总结需求

接口参数怎么设置

主要包括:

Path 参数:URL 路径中的变量,如 /user/{id}

Query 参数:URL 查询字符串,如 ?page=1&size=10

Header 参数:如 Content-Type: application/json、Authorization: Bearer token

Body 参数:POST/PUT 请求中发送的数据,通常 JSON 或 form-data

pytest 和 unittest的区别

unittest 是 Python 内置的单元测试框架,规范统一,不过语法较繁琐,扩展性弱
pytest 是第三方库, 更轻量、更灵活,扩展性更高, 更适合复杂项目

什么是冒烟测试?

冒烟测试是对新构建的软件进行一次核心功能的快速检查, 确保系统能正常启动、主要功能能跑通,
目的是验证这个版本是否具备进一步测试的条件; 如果冒烟测试失败,就不进行后续的测试

性能测试怎么做?需要关注哪些指标?

  1. 性能测试流程

    • 进行需求分析:明确测试目标与关键指标,确定测试范围
    • 搭建测试环境,准备测试数据
    • 编写脚本,设置参数等,模拟用户操作
    • 执行压测:分阶段增加并发用户,监控服务器资源及中间件状态
    • 分析结果:对比实际指标与阈值,找出性能瓶颈
    • 优化:根据瓶颈点进行优化,再次回归验证

2.关注的指标:
响应时间、吞吐量、资源利用率,持续运行能力等

打开网页,要响应10秒,你觉得可能的原因

  1. 用户端网络质量差
  2. DNS 解析延迟
  3. 服务器处理慢
  4. 资源体积太大,比如图片, 或者数量过多
  5. CDN 故障或配置问题

你了解 Postman 吗?是干啥的?除了接口测试功能还有哪些?

主要功能是 发送HTTP 请求,实现接口功能测试

其他功能:

  • 集合测试(一组 API 请求的集合), 可以将接口按业务场景分组,一键运行整个集合, 还支持批量执行与自动化

  • Mock 服务:就是在后端接口未完成的时候, 可模拟接口返回数据, 提供给前端测试

  • 文档生成:自动生成接口文档,还支持分享协作

  • 环境与变量管理, 支持多环境(比如开发 / 测试 / 生产)配置,可以通过全局变量实现参数传递, 比如用户 token

  • 监控功能, 可以对接口设置监控,出现异常可以提示

Linux 查看日志上下五行的命令

grep结合上下文参数:

  • grep -C 5 "关键词" 日志文件:显示匹配行及上下各 5 行(C=Context)。
  • 其他常用:-A 5(后 5 行,A=After)、-B 5(前 5 行,B=Before)。
    示例:grep -C 5 "error" /var/log/app.log(查看 app.log 中含 “error” 的行及上下 5 行)。

你对ai的了解 你常用哪个ai 哪个好用?

我对 AI 的理解是:它能够大幅度提高效率,比如在代码开发、文档编写、数据分析等方面。主要用过 ChatGPT 和 豆包

ChatGPT 在学习和解决问题上很有帮助,比如快速理解新知识、生成示例代码;而 豆包 的中文处理能力更突出,知识运用与数学能力更好

我觉得不同 AI 各有优势,如果是学习和技术交流,ChatGPT 更好用;如果是日常沟通和知识处理,豆包更合适。
我觉得 AI 好用的点是,能帮助我们快速定位问题和提供思路,但最后的逻辑和实现还是需要我们自己把控。

常用的 Docker 命令?

  • docker pull 拉取镜像
  • docker create 创建容器
  • docker rm 删除容器
  • docker ps 列出正在运行的容器列表
  • docker run 创建容器并运行指定命令
  • docker start 启动容器
  • docker stop 停止运行容器
  • docker restart 重启容器
  • docker rm 删除容器
  • docker exec 容器执行指定命令
  • docker rmi 删除镜像

镜像和容器是什么?有什么区别?

镜像是一个 只读的模板,包含了运行应用需要的所有内容:比如操作系统环境、应用程序、依赖库、配置等。镜像是静态的,容器是动态的,镜像不能直接运行

容器是 镜像运行起来后的实例,带有运行时环境,可以启动、停止、删除。

Docker 有哪些优缺点?

优点:

部署方便
隔离性好
轻量级、启动快

缺点

对系统内核依赖强: Docker 容器共享宿主机内核
运维复杂度提升:大规模容器化场景,需要镜像管理、版本管理、监控、日志收集,否则容易失控

selenium定位元素的8个方法是什么?

Selenium 一共有 8 种常用的元素定位方法:id、name、class_name、tag_name、link_text、partial_link_text、xpath、css_selector

定位不到元素的原因

1、可能是页面加载延迟,这个需要通过等待延迟的方式来处理

2、不过有时候,页面加载完成,但是元素暂时还不可见,导致定位不成功
这个可以选择使用显示等待来处理,可以用 WebDriverWait 类来实现

3、还有就是像内嵌网页的问题,需要使用 driver.switch_to.frame (name/index) 这个函数来跳转到处理。

4、还有要注意多窗口问题,动态 id 问题等的问题,对于多窗口处理,可以使用 driver.switch_to.window () 的方式来进行处理,而对于动态 id 的问题,需要注意的是有些 id 跟数字有关,可能会动态变化,可以使用 xpath 也可以使用 css_select 属性定位或者样式定位,或者可以通过父元素来找元素,或者通过兄弟节点来找对应的元素。等等

5、还有要特别注意滚动条的问题,这里通过调用 js 代码来实现的,driver.execute_script (js)

6、再这就是有时候会碰到某些元素的是不可见的,比如 display 属性为 none 这就需要通过 java Script 修改 display 的值。

元素定位,有时候定位得到,有时候定位不到,可能是什么原因,你会怎么处理?

1、可能是网络问题,导致页面加载延迟,这个可以做延迟等待,一般选择隐式等待,在脚本前面加 上 driver.implicitly_wait(20)

2、也有可能是页面结构发生变化导致的,这个时候最好选择通过 xpath或css结合属性进行或者样式定位,或者采用 JQuery定位的方式来进行定位元素

Vue/React 动态渲染的元素怎么定位?

  • 使用显式等待,保证元素加载完成再操作;

  • 寻找稳定的属性,比如 data-testid 或 data-* 作为定位标识;

  • 可以使用xpath通过元素的文本内容定位

  • 通过层级关系定位

网络面试题

HTTP 和 HTTPS 的区别

http是明文传输,容易被窃听、篡改和伪造。https是加密传输,可以防止攻击

http默认的是80端口, https是443端口

传输敏感信息一般用 HTTPS

HTTP/1.0、HTTP/1.1、HTTP/2.0、HTTP/3.0 的区别?

HTTP/1.0 → 是短连接,每次请求都要建立 TCP,效率低

HTTP/1.1 → 是长连接,一个TCP连接可以发送多次请求,但有队头阻塞问题

HTTP/2.0 → 实现了多路复用,一个TCP连接可以处理多个请求, 解决了1.1的请求队头阻塞,但底层仍存在TCP的队头阻塞,引入了二进制传输 + 头部压缩

HTTP/3.0 → 基于 QUIC,使用UDP代替TCP, 彻底解决 TCP 队头阻塞,支持 0-RTT,更快更安全。

HTTP 常见状态码有哪些?

200 OK:请求成功。

201 Created: POST 新建资源成功。

301 永久重定向:资源位置永久改变,浏览器会缓存

302 临时重定向:临时跳转,常见于登录后跳转

400 Bad Request:请求参数错误

401 Unauthorized:未认证或 token 无效

403 Forbidden:认证了但没有权限

404 Not Found:资源不存在

500 Internal Server Error:服务端程序错误。

502 Bad Gateway:网关/代理收到无效响应(常见于 Nginx)

HTTP 请求头中包含什么?

大体分为四类:
通用信息(Host、User-Agent、Accept 等)
缓存与来源控制(Cache-Control、Referer)
认证与安全(Authorization、Cookie)
以及请求体相关(Content-Type、Content-Length)

HTTP 是基于 TCP 还是 UDP?

HTTP/1.x , HTTP/2 都基于 TCP;而 HTTP/3 基于 UDP

HTTP 长连接 vs. 短连接的区别是?

短连接:请求一次建立一次连接,用完就断;

长连接:多个请求复用一个连接,性能更好

从「敲下一个 URL」到「页面出现在屏幕」整条链路全景

从输入 URL 开始,浏览器会先检查缓存,再进行 DNS 解析得到服务器 IP,通过 TCP(三次握手)和 TLS(若 HTTPS)建立连接,发起 HTTP 请求。服务器接收到请求后处理业务逻辑并返回响应,浏览器解析响应数据,构建 DOM 和 CSSOM,生成渲染树,经过布局、绘制和合成,最终由 GPU 将页面呈现到屏幕上。 然后断开链接 四次挥手

GET 与 POST 有什么区别

GET 用于获取数据,参数在 URL,幂等且可缓存;

POST 用于提交数据,参数在请求体,非幂等,不易缓存。

GET 适合查询,POST 适合创建/更新资源。

幂等(多次请求结果相同,不改变服务器状态)

非幂等(多次提交可能创建多条数据或触发多次操作)

HTTP vs. HTTPS 有什么区别?

HTTP以明文形式传输,传输的数据可能会被窃听、篡改、仿造。

HTTPS在 HTTP 上加了 TLS/SSL 加密。更安全, 适合敏感信息传输

WebSocket 简介 & 与 HTTP 的核心区别

WebSocket 是基于 TCP 的全双工长连接协议,建立连接后客户端和服务器可以互相发送数据,适合实时高频场景。

HTTP 是单向请求-响应协议,客户端必须先发起请求,服务器才能响应。

WebSocket数据传输开销更小,效率更高。”

TCP 与 UDP 的区别?

TCP 是面向连接、可靠的传输协议,保证数据完整、顺序、无重复,但开销大,适合需要可靠传输的场景, 比如文件传输, 远程登录

UDP 是无连接、不可靠协议,开销小、传输快,适合对实时性要求高、能容忍少量丢包的场景, 比如视频直播、在线游戏、语音通信

TCP 的三次握手

第一次握手:客户端向服务器端发送SYN,确认客户端的发送没问题。

第二次握手:服务器向客户端发送SYN和ACK包,确认服务器的发送与接收没问题

第三次握手:客户端最后回复一个ACK包,确认客户端的接收没问题。

数据包在网络中传输的过程

  1. 发送方将应用数据封装成数据包,添加源地址,发送方法,传输协议类型,目的地址等

  2. 选择路由转发数据包,发送到目标主机

  3. 接收端解密数据包,恢复成原始数据

ip地址的划分

IP地址主要分为 IPv4 和 IPv6,日常使用更多的是 IPv4。IPv4 通过“网络位”和“主机位”来区分,用于实现网络分层管理和设备寻址。

IPv4 按类别划分为 A/B/C/D/E 类:

  • A 类(1.0.0.0–126.255.255.255)用于大型网络,网络位 8 位,主机位 24 位;
  • B 类(128.0.0.0–191.255.255.255)用于中型网络,网络位 16 位,主机位 16 位;
  • C 类(192.0.0.0–223.255.255.255)用于小型网络,网络位 24 位,主机位 8 位;
  • D 类用于组播,E 类为保留地址。

现代网络更常用 无类别域间路由,通过“IP 地址/子网掩码长度”表示,例如 192.168.1.0/24,其中前 24 位为网络位,后 8 位为主机位,可灵活调整网络和主机数量,提高地址利用率。

数据库

1.聚集索引的数据和索引放在一起,一个表只能有一个聚集索引,适合范围查询
非聚集索引的数据和索引分离,索引指向数据,一个表可以有多个非聚集索引,适合精确查询

2.B+ tree的优势(和哈希表,二叉树对比):高度低,磁盘io次数少; 查询高效,而且效率稳定,时间复杂度低

3.索引是一种数据结构,可以提高检索效率,降低io成本,减少cpu消耗
MySQL什么字段适合建立索引?什么不适合?
✅ 适合建立索引的字段
• 经常作为 查询条件(WHERE) 的字段
• 经常用作 排序(ORDER BY)、分组(GROUP BY)、连接(JOIN) 的字段。
• 区分度高的字段,比如身份证号、手机号。
• 频繁被访问 的字段。
❌ 不适合建立索引的字段
• 数据重复度高(如性别、是否删除标志位)。
• 很少用在查询条件中 的字段。
• 频繁更新的字段(会增加索引维护成本)。
• 大文本字段(如 TEXT、BLOB)。

4.索引的缺点:执行增删改操作时效率变低,占用额外存储空间

5.索引失效的情况:模糊匹配的时候以%开头; 对列进行函数运算或表达式计算;字符串不加引号; or连接的条件,一边有索引一边无索引;

6.唯一索引:加速查询 + 列值唯一(可以有 NULL)。
联合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并

索引类型(按照物理结构):聚簇索引、非聚簇索引。
索引类型(按照功能分类):主键索引、唯一索引、全局索引、复合索引、普通索引。

7.事务四大特性(acid): 原子性(回滚日志实现),一致性(通过其他三者实现),持久性(重做日志实现,隔离性(锁机制和mvcc实现)

8.事务隔离级别: 读未提交,读已提交,可重复读,串行化; mysql是默认可重复读

9.mysql默认存储引擎:innodb,综合处理能力和性能最好
innodb支持事务,myisam,memory不支持
innodb支持行级锁和表锁,其他两个只有表级锁
innodb支持外键,其他的不支持
innodb有崩溃恢复机制,其他的没有

innoDB适合高并发写操作;myisam适合读多写少的场景 memory用内存存储数据,访问速度快但数据容易丢失
10.EXPLAIN 命令可以分析 SQL 的 执行计划

11.mysql连接分为内连接和外连接,
内连接只返回两张表中都匹配的记录;
外连接分为左外连接和右外连接:
左外连接返回左表所有行,右表没匹配到的记录用null填充,
右外连接返回右表所有行,左表没匹配到的记录用null填充

12.count(1)、count() 与 count(列名) 的区别?:count(1)、count() 都是统计所有行数,COUNT(列名)会忽略值为null的行

13.覆盖索引是什么?: 查询的所有字段都能从索引本身获取,不需要回表。可以减少一次主键查询,性能更高

14.回表是什么: 通过二级索引(除了主键索引之外的索引)找到主键,再通过主键索引(聚簇索引)查找数据的过程。

15.最左前缀原则: 查询条件必须从索引最左边的列开始匹配,并且连续匹配,索引才会生效. 如果中间某个列不在查询条件中,后面的索引会失效

16.drop、delete 与 truncate 的区别?
DROP 用来删除整张表,包括表结构,不能回滚。

TRUNCATE 用于清空表中的所有数据,但会保留表结构,不能回滚。

DELETE 用来删除行,可以带 WHERE 条件,可以回滚。

17.sql查询的执行顺序: 先执行 FROM 确定主表,再执行 JOIN 连接,然后 WHERE 进行过滤,接着 GROUP BY 进行分组,HAVING 过滤聚合结果,SELECT 选择最终列,ORDER BY 排序,最后 LIMIT 限制返回的行数

  1. InnoDB 是如何存储数据的?
    InnoDB 的数据按行存储在页(16KB)中,页组成段,段存放在表空间中。
    数据通过聚集索引存储,二级索引存储主键引用。
    InnoDB 支持事务和 MVCC,使用 Redo Log 和 Undo Log 实现崩溃恢复和事务回滚,同时通过缓冲池加速磁盘读写。

  2. Hash 索引与 BTree 索引有什么区别?
    Hash 索引基于哈希表实现,支持等值查询,查询速度快,不支持范围查询和排序操作;
    BTree 索引基于平衡树结构,支持等值查询、范围查询和排序操作,适合大多数查询场景

  3. SQL 聚合函数有哪些?
    COUNT、SUM、AVG、MIN、MAX等,用于对数据集进行汇总和统计分析。

http://www.dtcms.com/a/469993.html

相关文章:

  • 做外贸网站做成哪种形式好WordPress购物个人中心
  • LeetCode 395 - 至少有 K 个重复字符的最长子串
  • 科技有限公司可以做网站建设吗成都网站网络建设
  • Qt绘制折线图
  • Idea中新建package包,变成了Directory
  • 如何自建淘宝客网站wordpress 知笔墨
  • Python爬虫实战:腾讯控股2024年资产负债分析
  • AI-调查研究-100-具身智能 现代AI方法全解析:强化学习、模仿学习与Transformer在机器人控制中的应用
  • Docker核心技术:深入理解网络模式 ——Host/None/Container 模式与混合云原生架构实践
  • 南通市住房城乡建设局网站磁力蜘蛛种子搜索
  • 解决HTML塌陷的方法
  • sqlite 使用: 03-问题记录:在使用 sqlite3_bind_text 中设置 SQLITE_STATIC 参数时,处理不当造成的字符乱码
  • 网站建设与维护难不难为什么找别人做网站
  • 广州木马网站建设公司医院门户网站建设规划
  • 大模型学习之 深入理解编码器与解码器
  • pyqt 触摸屏监听
  • C++ Primer Plus 第六版 第十三章 编程题
  • 大模型前世今生(十二):Hessian矩阵
  • 蛙跳积分法:分子动力学模拟中的高效数值积分技术
  • 详解 SNMPv1 与 SNMPv2 Trap 格式
  • 书法网站建设成都微信公众号制作
  • 宜春网站制作公司wordpress图片上传慢
  • Python串口通信与MQTT物联网网关:连接STM32与物联网平台
  • MyLanViewer(局域网IP扫描软件)
  • 湛江专业建站推荐40平米小户型装修效果图
  • 147.《手写实现 Promise.all 与 Promise.race》
  • 【HarmonyOS】异步并发和多线程并发
  • 使用docker 安装dragonfly带配置文件(x86和arm)版本
  • 企业信息型网站有哪些网站建设塞西
  • 怎么看网站是什么程序做的益阳网络