Python测试用例中如何避免setUp()为每个测试方法启动浏览器?
我在练习编写Web自动化测试用例,针对GitHub的登录、用户主页用户名验证及登出功能编写了测试方法。但unittest框架的setUp()会在每个测试方法前初始化,导致每个测试方法都会打开新浏览器,我希望所有测试方法在同一浏览器会话中继续测试。
我尝试通过类属性initialized和completed控制setUp()不重复加载浏览器,以及tearDown()不在所有测试完成前关闭浏览器,但每个测试函数仍会打开一个浏览器(共3个)。观察打印结果发现:test_login中self.completed和self.initialized都为1,但test_username_presence中self.completed重置为0,self.initialized为1,说明每个测试方法都会创建新的类实例,实例属性被重置。我还尝试用setUpClass(cls)初始化属性,以及自定义__init__方法,但结果相同,后两个测试打开的是无URL的空白页面。
以下是我尝试的代码:
import unittest from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.expected_conditions import element_to_be_clickable class GitHubLoginTest(unittest.TestCase): initialized = 0 completed = 0 def setUp(self): if self.initialized < 1: self.initialized = 1 chrome_options = Options() chrome_options.add_experimental_option("detach", True) self.driver = webdriver.Chrome(options=chrome_options) else: pass def test_login(self): driver = self.driver driver.get("https://github.com") driver.find_element(By.LINK_TEXT, "Sign in").click() username_box = WebDriverWait(driver, 10).until(element_to_be_clickable((By.ID, "login_field"))) username_box.send_keys("<username>") password_box = driver.find_element(By.NAME, "password") password_box.send_keys("<password>") password_box.submit() self.completed += 1 print(self.completed) print(self.initialized) def test_username_presence(self): print(self.completed) print(self.initialized) self.assertIn("SubjectofthePotentate", self.driver.page_source) self.driver.find_element(By.CLASS_NAME, "AppHeader-user").click() profile_label = self.driver.find_element(By.CLASS_NAME, "lh-condensed") user_label = profile_label.get_attribute("innerHTML") print(user_label) self.assertIn("SubjectofthePotentate", user_label) self.completed += 1 print(self.completed) def test_logout(self): self.driver.find_element(By.CLASS_NAME, "DialogOverflowWrapper") self.driver.find_element(By.ID, ":r11:").click() sign_out = WebDriverWait(self.driver, 10).until(element_to_be_clickable((By.NAME, "commit"))) sign_out.click() self.completed += 1 print(self.completed) def tearDown(self): if self.completed == 3: self.driver.close() else: pass if __name__ == "__main__": unittest.main()
问题根源
unittest框架会为每个测试方法创建一个独立的测试类实例,所以你定义的self.completed、self.initialized是实例属性,每个测试方法运行时都会重新初始化,导致无法跨测试保持状态。而你之前判断的是self.initialized(实例属性),而非类级属性,所以每个新实例都会触发浏览器初始化。
正确实现方式
使用unittest的setUpClass(cls)和tearDownClass(cls)类方法,它们会在所有测试方法执行前运行一次和所有测试方法执行后运行一次,用来初始化和销毁共享的浏览器会话。同时将driver设为类属性,让所有测试实例共用同一个浏览器对象。
修改后的代码如下:
import unittest from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.expected_conditions import element_to_be_clickable, presence_of_element_located class GitHubLoginTest(unittest.TestCase): # 类属性,所有实例共享 driver = None @classmethod def setUpClass(cls): # 仅在所有测试前初始化一次浏览器 chrome_options = Options() chrome_options.add_experimental_option("detach", True) cls.driver = webdriver.Chrome(options=chrome_options) cls.driver.maximize_window() def test_login(self): driver = self.driver driver.get("https://github.com") # 等待Sign in按钮可点击 sign_in_btn = WebDriverWait(driver, 10).until(element_to_be_clickable((By.LINK_TEXT, "Sign in"))) sign_in_btn.click() # 输入用户名密码 username_box = WebDriverWait(driver, 10).until(element_to_be_clickable((By.ID, "login_field"))) username_box.send_keys("<你的GitHub用户名>") password_box = driver.find_element(By.NAME, "password") password_box.send_keys("<你的GitHub密码>") password_box.submit() # 验证登录成功(等待主页元素加载) WebDriverWait(driver, 10).until(presence_of_element_located((By.CLASS_NAME, "AppHeader-user"))) def test_username_presence(self): driver = self.driver # 点击用户头像 user_avatar = WebDriverWait(driver, 10).until(element_to_be_clickable((By.CLASS_NAME, "AppHeader-user"))) user_avatar.click() # 获取用户名标签 profile_label = WebDriverWait(driver, 10).until(presence_of_element_located((By.CLASS_NAME, "lh-condensed"))) user_label = profile_label.text.strip() self.assertEqual(user_label, "SubjectofthePotentate", "用户名验证失败") def test_logout(self): driver = self.driver # 确保用户菜单已展开(如果之前没展开) try: driver.find_element(By.CLASS_NAME, "Dropdown-menu") except: user_avatar = WebDriverWait(driver, 10).until(element_to_be_clickable((By.CLASS_NAME, "AppHeader-user"))) user_avatar.click() # 找到Sign out选项(避免用动态ID,改用文本定位) sign_out_btn = WebDriverWait(driver, 10).until(element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Sign out')]"))) sign_out_btn.click() # 验证登出成功 WebDriverWait(driver, 10).until(element_to_be_clickable((By.LINK_TEXT, "Sign in"))) @classmethod def tearDownClass(cls): # 所有测试完成后关闭浏览器 if cls.driver: cls.driver.quit() if __name__ == "__main__": unittest.main()
关键修改点说明
- 使用
setUpClass和tearDownClass:这两个类方法仅执行一次,分别负责初始化浏览器和销毁会话。 - 类属性
driver:所有测试方法通过self.driver访问同一个浏览器实例,实现会话共享。 - 优化元素定位:替换动态ID(如
:r11:)为更稳定的定位方式(如XPath文本匹配),避免页面元素ID变化导致测试失败。 - 增加等待逻辑:在关键步骤添加显式等待,确保页面元素加载完成后再操作,提升测试稳定性。
内容的提问来源于stack exchange,提问作者jacob malu

