背景:在進行javaweb項目開發時,通過登錄設備,調用不同的測試用例(對設備下發命令,獲取回顯信息),判斷業務是否達到預期效果。利用python的telnet模塊進行實現較為方便,具體實踐時也遇到一系列問題,主要包括:(1)java調用python的參數傳遞與實時回顯問題; (2)python日志模塊的重復打印問題
1 java調用python
關於java調用python的方法,常見的有2種。第一種:使用java的Process類調用python腳本,然后讀取回顯信息,此方法的缺點是對於python的回顯信息反應較慢,無法滿足實時回顯需求。
第二種方法,利用Jython執行python方法,優點:能獲取python方法的返回值,作為java調用python其他方法的入參,較好地控制代碼流程,能更好的滿足項目需求,同時,它能及時的獲取python方法的返回值,因此,強力推薦該方法。
第一種方法代碼如下:
1 public static void main(String[] args) 2 { 3 // 1. 根據用戶輸入設備信息、選擇用例修改腳本 4 String shellPath = "D:\\dDevelopemnt\\javaDemo\\project1\\clockDetect\\src"; 5 6 Runtime run = Runtime.getRuntime(); 7 8 // 2. 執行cmd 9 try 10 { 11 // run.exec("cmd /k shutdown -s -t 3600"); 12 // cmd.exe /k 13 14 Process process = run.exec(" cmd /c D: && cd " + shellPath + " && python clkChecker.py fdas fda 432"); 15 16 InputStream input = process.getInputStream(); 17 BufferedReader reader = new BufferedReader(new InputStreamReader(input, Charset.forName("GBK"))); 18 String szline; 19 System.out.println("\n********** DOS info begin **********"); 20 while ((szline = reader.readLine()) != null) 21 { 22 System.out.println(szline); 23 } 24 System.out.println("********** DOS info end **********\n"); 25 26 // 第四步:輸出Log,移動到指定路徑 27 28 reader.close(); 29 process.waitFor(); 30 process.destroy(); 31 } 32 catch (Exception e) 33 { 34 e.printStackTrace(); 35 } 36 }
第二種方法代碼如下:
java側代碼
1 public void exeTestCases( 2 ArrayList<String> lstCaseNames, 3 String projectName, 4 5 String deviceAip, 6 String deviceAport, 7 String deviceAusername, 8 String deviceApasswd, 9 10 String deviceBip, 11 String deviceBport, 12 String deviceBusername, 13 String deviceBpasswd){ 14 15 String shellPath = "D:\\dDevelopemnt\\javaDemo\\project1\\clockDetect\\src\\clkJython.py"; 16 17 Properties props = new Properties(); 18 props.put("python.console.encoding", "UTF-8"); // Used to prevent: console: Failed to install '': java.nio.charset.UnsupportedCharsetException: cp0. 19 props.put("python.security.respectJavaAccessibility", "false"); //don't respect java accessibility, so that we can access protected members on subclasses 20 props.put("python.import.site","false"); 21 Properties preprops = System.getProperties(); 22 PythonInterpreter.initialize(preprops, props, new String[0]); 23 PythonInterpreter interpreter = new PythonInterpreter(); 24 interpreter.exec("import sys"); 25 interpreter.exec("sys.path.append('D:\\dDevelopemnt\\javaDemo\\project1\\clockDetect\\src')");//自定義庫函數路徑 26 interpreter.execfile(shellPath); 27 28 // 前端設備信息封裝 29 JSONObject jsondata = new JSONObject(); 30 jsondata.put("\"projectName\"","\""+projectName+"\""); 31 jsondata.put("\"deviceAip\"","\""+deviceAip+"\""); 32 jsondata.put("\"deviceAusername\"","\""+deviceAusername+"\""); 33 jsondata.put("\"deviceApasswd\"","\""+deviceApasswd+"\""); 34 jsondata.put("\"deviceAport\"","\""+deviceAport+"\""); 35 jsondata.put("\"deviceBip\"","\""+deviceBip+"\""); 36 jsondata.put("\"deviceBusername\"","\""+deviceBusername+"\""); 37 jsondata.put("\"deviceBpasswd\"","\""+deviceBpasswd+"\""); 38 jsondata.put("\"deviceBport\"","\""+deviceBport+"\""); 39 jsondata.put("\"lstCaseNames\"","\""+lstCaseNames+"\""); 40 41 System.out.println(jsondata.toString()); 42 43 // 調用Python方法傳遞參數、返回參數 44 // 第一個參數為期望獲得的函數(變量)的名字,第二個參數為期望返回的對象類型 45 PyFunction pyFunction = interpreter.get("setConfig", PyFunction.class); // 1.前端傳遞設備類表及參數信息 46 47 //調用函數,如果函數需要參數,在Java中必須先將參數轉化為對應的“Python類型” 48 PyObject pyobjDictInfo = pyFunction.__call__(new PyString(jsondata.toString())); // 2.返回Python側封裝的設備信息 49 50 // 前端選取的測試用例 51 for(String item:lstCaseNames){ 52 System.out.println("-----"+item+"------"); 53 PyFunction fun1 = interpreter.get(item, PyFunction.class); // 3.調用執行用例1 54 PyObject pyobjDictSync = fun1.__call__(pyobjDictInfo); // 4. 獲取執行用例1的結果 55 System.out.println("****"+item+" now exe finished! "+ item + new Date().toString() ); 56 57 // 將消息推送至客戶端,告知釋放的設備ip 58 try { 59 WebSocketServer.sendInfo(pyobjDictSync.toString()); 60 } catch (IOException e) { 61 e.printStackTrace(); 62 } 63 } 64 65 66 }
上述29-39行是java側封裝成標准json格式數據
44-48行是java側調用python方法,進行設備的登錄,涉及項目進行參數的配置(設備用戶名、密碼、ip、項目名稱等入參)
pyobjDictInfo是python側封裝的字典數據,包括設備列表以及Telnet對象等,該參數作為執行測試用例的入參,
51-63行是遍歷測試用例
python側代碼
1 ''' 2 通過Jython調用 3 ''' 4 def setConfig(data): 5 res= json.loads(data) # 根據字符串書寫格式,將字符串自動轉換成 字典類型 6 7 global projectName 8 projectName = eval(res['"projectName"']) 9 logDir = "d:\\loggg\\" + projectName+"_log.txt" 10 11 # 創建一個logger 12 global root_logger 13 global fh 14 global ch 15 16 root_logger = logging.getLogger("clkLogger") 17 root_logger.setLevel(logging.DEBUG) 18 19 # 創建一個handler,用於寫入日志文件 20 fh = logging.FileHandler(logDir) 21 fh.setLevel(logging.DEBUG) 22 23 # 再創建一個handler,用於輸出到控制台 24 ch = logging.StreamHandler() 25 ch.setLevel(logging.DEBUG) 26 27 # 定義handler的輸出格式 28 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 29 fh.setFormatter(formatter) 30 ch.setFormatter(formatter) 31 32 # 給logger添加handler 33 root_logger.addHandler(fh) 34 root_logger.addHandler(ch) 35 logger = root_logger; # 修改全局日志變量 36 37 logger.info("======================================== project "+projectName+" begin ========================================") 38 logger.debug(res) 39 40 dictDevice = {} 41 dictDevice['key1'] = [eval(res['"deviceAip"']) , eval(res['"deviceAusername"']),eval( res['"deviceApasswd"']), eval(res['"deviceAport"'])] 42 dictDevice['key2'] = [eval(res['"deviceBip"']), eval(res['"deviceBusername"']), eval(res['"deviceBpasswd"']), eval(res['"deviceBport"']) ] 43 44 logger.info("==================== login device begin ====================") 45 myclass = MyClass(); 46 dictInfo = myclass.tearUp(dictDevice) 47 48 return dictInfo 49 50 51 # 測試用例1 52 def checkClk(dictInfo): 53 logger.info("==================== 1.1基本功能 begin ====================") 54 lstInfo = clearConfigInfo(dictInfo) 55 dict = detect_clk_sync(lstInfo[0],lstInfo[1],lstInfo[2],lstInfo[3]) 56 logger.debug(dict) 57 logger.info("==================== 1.1基本功能 end ====================") 58 return dict 59 60 # 測試用例2 61 def checkClkfreq(dictInfo): 62 logger.info("==================== 1.2測試用例2 begin ====================") 63 lstInfo = clearConfigInfo(dictInfo) 64 dict = detect_clk_freq_deviation(lstInfo[0],lstInfo[1],lstInfo[2],lstInfo[3]) 65 logger.debug(dict) 66 logger.info("==================== 1.2測試用例2 end ====================") 67 return dict
其中,46行處調用函數如下,返回值包含telnet對象
1 # 登錄設備 2 def tearUp(self,dictDevice): 3 dictInfo = {} 4 5 6 lstA = dictDevice['key1'] 7 lstB = dictDevice['key2'] 8 deviceA = [lstA[0],lstA[1],lstA[2],lstA[3]]; 9 deviceB = [lstB[0],lstB[1],lstB[2],lstB[3]]; 10 11 try: 12 logger.info(deviceA) 13 logger.info(deviceB) 14 15 telnetA = telnetlib.Telnet(deviceA[0], port=23, timeout=50) 16 loginsys(telnetA,deviceA[1],deviceA[2]) 17 # print deviceInfo(telnetA) # 獲取設備信息 18 19 telnetB = telnetlib.Telnet(deviceB[0], port=23, timeout=50) 20 loginsys(telnetB,deviceB[1],deviceB[2]) 21 22 dictInfo["telnetA"] = telnetA 23 dictInfo["telnetB"] = telnetB 24 dictInfo["deviceA"] = deviceA 25 dictInfo["deviceB"] = deviceB 26 27 logger.info("------ login success ------") 28 29 except(SystemExit, KeyboardInterrupt): 30 logger.info("------ ping error ------") 31 32 except Exception as e: 33 logger.error("failed to ping", exc_info=True) 34 35 return dictInfo
2 日志的重復打印問題
背景:在前端配置頁面,輸入項目項目,后端python解析后,以該項目名稱作為log名稱。

因此,在每次點擊“提交”按鈕都會執行python側的setConfig函數,導致日志模塊會不斷添加handler,重復打印日志,
如下圖:

這顯然不利於日志處理,隨着測試項目的不斷增多,重復的次數也會不斷增加。
因此,需要在每次執行完一個項目時,就刪除日志的handler模塊,具體實現函數為
1 ''' 2 測試用例執行完畢,調用此方法 3 ''' 4 def tear_down(): 5 logger.debug("close the logging handlers") 6 root_logger.removeHandler(fh) 7 root_logger.removeHandler(ch) 8 logger.removeHandler(fh) 9 logger.removeHandler(ch)
