Python执行Selenium测试时JPype(JVM)多实例启动报错咨询
JPype 多测试类启动JVM报错解决方案
问题现象
基于Selenium+Python开发自动化测试框架,对接Oracle数据库时采用JayDeBeApi+JPype实现连接,执行多个测试用例时,setUpClass中调用的jpype.startJVM()触发OS error: JVM cannot be restarted报错。
已知Python进程整个生命周期内仅能启动一个JVM实例,最初将jpype.startJVM()、jpype.shutdownJVM()分别放在测试父类的setUpClass、tearDownClass中(预期两个方法整个测试流程仅执行1次),但方案未生效。
原有代码结构
JVM初始化测试父类
class ParentTest(unittest.TestCase): @classmethod def setUpClass(self): jpype.startJVM(jpype.getDefaultJVMPath(), Constant.ojdbcpath) print('› Test starting and JVM turned on') @classmethod def tearDownClass(self): jpype.shutdownJVM() print('> Test finished and JVM closed')
测试用例实现
class Example_Test (ParentTest): driver = None def setUp(self): self.driver = webdriver.Chrome(Constant.driver) self.driver.set_window_size(Constant.screen_width, Constant.screen height) def tearDown(self): self.driver.close() self.driver.quit() def test example(self) : print ('> Executing test case...')
数据库查询方法
def get_status(_parameter): with Database() as db: query = "SELECT * FROM TABLE WHERE COLUMN = " + str(_parameter) results = db.query(query) return str(results[0][0])
数据库连接封装类
class Database: def __init__ (self): self._connection = None self._cursor = None def __enter__ (self): try: self.connection = jaydebeapi.connect(Constant.dbdriver, Constant.ldap, [Constant.dbuser, Constant.dbpassword]) # print("› Connected to database!') self._cursor = self._connection.cursor() except Exception as err: print (err) return self def __exit__ (self, exc_type, exc_val, exc_tb): try: self._cursor.close() # print("› Closed cursor connection!") self._connection.close() # print("› Closed database connection! ') except Exception as err: print (err) return self def query (self, sql): try: self._cursor.execute (sql) self.result = self._cursor.fetchall() return self.result except Exception as err: print (err)
测试执行入口
test1 = TestLoader().loadTestsFromtestCase(...) test2 = TestLoader().loadTestsFromtestCase(...) suite = TestSuite([test1, test2]) h = HTMLTestRunner(output=FOLDER).run(suite)
根因分析
setUpClass/tearDownClass的执行粒度是单个测试类,不是全局整个测试套件:
- 每加载执行一个测试类,就会运行一次对应类的
setUpClass - 单个测试类所有用例执行完,就会运行
tearDownClass
套件里包含多个独立测试类时,第一个测试类跑完会直接关闭JVM,第二个测试类再尝试启动JVM时就会触发报错——JPype不支持JVM关闭后在同一个进程内重启。
另外现有Database类封装存在变量名笔误:__enter__方法中给self.connection赋值,但后续初始化游标、关闭连接都用的是self._connection,运行时会触发属性不存在错误。
修复方案
核心原则:JVM全局仅启动一次,所有测试执行完再关闭,不要绑定到单个测试类的生命周期。
- 全局启动JVM,增加启动状态判断
把JVM启动逻辑移到测试入口最开头,组装测试套件前执行,启动前先判断是否已启动,避免重复调用:if not jpype.isJVMStarted(): jpype.startJVM(jpype.getDefaultJVMPath(), Constant.ojdbcpath) - 移除测试类中关闭JVM的逻辑
删掉ParentTest里tearDownClass的jpype.shutdownJVM()调用,单个测试类执行完不要关闭JVM,否则后续测试类的数据库操作会直接失败。 - 所有测试执行完成后统一关闭JVM
在测试套件全部运行结束后,再调用JVM关闭逻辑:# 套件run完成后执行 if jpype.isJVMStarted(): jpype.shutdownJVM() print('> All tests finished, JVM closed') - 修复Database类的笔误
把__enter__方法里的self.connection统一改为self._connection,和类中其他地方的变量名保持一致。
如果不想把JVM启动逻辑移到入口,也可以在父类加全局标记控制启动,修改后的ParentTest示例:
import jpype import unittest # 全局标记JVM启动状态 JVM_INITIALIZED = False class ParentTest(unittest.TestCase): @classmethod def setUpClass(cls): global JVM_INITIALIZED if not JVM_INITIALIZED and not jpype.isJVMStarted(): jpype.startJVM(jpype.getDefaultJVMPath(), Constant.ojdbcpath) JVM_INITIALIZED = True print('› JVM initialized for test suite') @classmethod def tearDownClass(cls): # 此处不关闭JVM,全局统一处理 pass
内容的提问来源于stack exchange,提问作者Diego Abraham
相关产品推荐
相关产品推荐

