摘 要:光收發模塊固件開發需根據不同的客戶需求而定制,使得測試用例不斷增加,因此長時間的壓力測試也日益增多,為了節省測試用例編寫時間,提出了用Python語言編寫光收發模塊固件測試腳本的方法。文中介紹了基于Python調用動態鏈接庫、構建測試環境、編寫測試用例腳本等內容。結果表明,選用Python易于編寫測試用例,提高了測試效率。
關鍵詞:光收發模塊;Python;測試腳本;動態鏈接庫;ctypes;SFF-8472
中圖分類號:TP311 文獻標識碼:A 文章編號:2095-1302(2024)06-0-03
0 引 言
光收發模塊是光通信系統的重要組成部分,是光纖通信系統中光電轉化的器件。本地終端將待傳輸的電信號由光收發模塊的電/光(E/O)轉換為適宜傳輸的光信號發送到光纖鏈路中,再由遠方終端的另一個模塊把接收的光信號經光收發模塊的光/電(O/E)轉換為電信號輸出給遠方終端主機,從而實現異地終端通信[1]。光收發模塊要滿足MSA協議的數字診斷監測和監控信號實時監測[2]、模擬量溫度補償以及系統I2C和突發Trigger中斷處理要求,因此固件(Firmware)測試是很重要的環節。
光模塊固件功能和性能測試是嚴謹、細致的工作,在固件交付給硬件工程師調測之前必須完成全面測試。具體測試方法主要包括白盒測試和黑盒測試。黑盒測試可稱為功能測試、數據驅動測試或基于需求說明的測試,是在完全不考慮程序內部結構和內部特性的情況下,檢查輸入與輸出之間的關系是否符合要求[3]。我們在完成固件白盒測試的條件下,黑盒測試主要是為滿足不同用戶對MSA多源協議的開放區域和加密區域需求以及面對潛在資源訪問沖突風險而進行的測試。如果采用人工手動測試,往往按照測試用例步驟反復操作,工作量很大,并且單調重復的工作容易出錯;其次,系統壓力測試、性能測試需要模擬用戶系統測試場景;最后,固件是否能長時間可靠運行也非手工測試可以執行。因此,需要軟件開發人員開發自動化測試方案。光模塊的自動化測試有幾種形式,最常見的是基于傳統的文本語言軟件開發環境,常用的有LabWindows/CVI、VB、VC++、C#等;一種是基于圖形化語言軟件開發環境,常用的有NI的LabVIEW編寫的GUI程序測試,或使用工具命令語言TCL編寫腳本測試[4]。軟件測試中,腳本技術的引入是實現軟件測試自動化技術的有效手段[5],Python易學易用,有更多模塊庫的支持,在編寫測試腳本中具有很大優勢。
Python是一種面向對象的解釋性高級編程語言,具有動態語義[6]。Python的優點是易讀易維護、可擴展、可移植、擁有豐富的庫支撐[7]。Python是開發腳本的絕佳工具,Python的最大缺點是其整體性能。作為一種解釋性語言,Python比一些編譯語言(如C)慢。在一些重視速度的任務中,會影響測試性能。但可以把Python腳本編寫優勢與其他語言的優勢融合,使用Python開發自動測試腳本,同時對于性能關鍵型和特定設備的驅動程序代碼則仍使用其他語言,比如C,通過調用C語言編寫的動態庫來克服其缺點。可以用Python的擴展工具ctypes。ctypes最初是Thomas Heller開發的一個項目,但現在包含在標準庫中,雖然存在一些限制,但這可能是訪問C語言代碼的最簡單方式之一,只需將庫導入就可使用。
1 光收發模塊測試接口
訪問光收發模塊監控量接口時遵循多元協議,即SFF-8472協議。xPon的OLT和ONU使用SFF-8472協議實現對光鏈路的測量和診斷[8]。A0和A2地址值是SFF-8472規定的從設備地址值,每個地址可訪問256 B數據,如圖1所示[9]。A2地址還可通過127字段來選擇擴展頁,其中128到255頁供光模塊生產商使用,一般用來調試接口或存儲數據。
2 搭建Python自動化測試腳本環境
2.1 Python調用動態鏈接庫的方法
Python調用C語言編寫動態鏈接庫,不僅要兼容C接口的調用習慣,還要兼容C語言的數據類型。而ctypes庫已經做了這兩方面工作,使得調用動態鏈接庫非常方便。ctypes導出cdll以及Windows下的windll和oledll對象,用于加載動態鏈接庫。通過訪問這3個對象的屬性,就可以調用動態鏈接庫的函數。cdll加載使用標準cdecl調用約定導出函數的庫,而windll庫使用stdcall調用約定函數。oledll使用stdcall調用約定,且返回值是Windows中返回的HRESULT值[10]。在Python程序中添加import ctype語句就可以導入ctypes庫。
2.2 cdll庫的加載
測試腳本需要加載2種不同調用約定方式的動態鏈接庫,一種是cdll,另一種是windll。按cdll調用約定的動態鏈接庫ATEAPI.dll是測試板驅動程序,主要由USB訪問、I2C讀寫、測試板電源控制等底層操作函數組成,該動態鏈接庫的加載如下:
self.objdll = ctypes.cdll.LoadLibrary(\".\ATEAPI.dll\")
2.2.1 測試板類的設計
可以設計一個類,在類中加載動態鏈接庫ATEAPI.dll,并增加屬性和方法,便于測試腳本調用,具體代碼如下:
class cTestEvb:
def __init__(self, devusbindex=0):
self.devUsbIndex = devusbindex
# load dll
self.objdll = ctypes.cdll.LoadLibrary(\".\ATEAPI.dll\")
# use property
def setUsbDevice(self, devusbindex):
self.devUsbIndex = devusbindex
def getUsbDevice(self):
return self.devUsbIndex
devusbindex = property(setUsbDevice, getUsbDevice)
# Open USB Device
def openUsbDevice(self):
usbHandle = None
usbHandle = self.objdll.AteOpenDevice(self.devUsbIndex)
if usbHandle != None:
print(\"Open USB device {}\".format(self.devUsbIndex))
self.objdll.AteCloseDevice(usbHandle)
# Slot Power On
def AteAllPowerOn(self):
self.objdll.AteSffPowerOn(self.devUsbIndex)
self.objdll.AteSfpPowerOn(self.devUsbIndex)
def AteAllPowerOff(self):
self.objdll.AteSffPowerOff(self.devUsbIndex)
self.objdll.AteSfpPowerOff(self.devUsbIndex)
在編寫測試腳本程序時,首先創建類的對象,然后打開USB設備和測試板電源,代碼如下:
testEvb = cTestEvb(devUsbIndex)
testEvb.openUsbDevice()
testEvb.AteAllPowerOn()
2.3 windll庫的加載
另一個動態鏈接庫是CommandIndex.dll,該庫可以完成一些測試命令的解析,通過在測試腳本輸入命令的方式測試光模塊,比直接操作光模塊A2擴展頁中的寄存器更加方便快捷,因為這樣無需反復查看固件工程師編寫的Memory map文件,只需輸入命令,經過CommandIndex.dll解析命令轉換為一幀含有包頭、包尾、累加和校驗的數據包。光模塊接收這幀數據并解析命令后調用對應函數或讀寫數據,再將讀數據打包,返回上位機。CommandIndex.dll的接口函數AteCmdSer完成命令輸入與數據輸出,函數原型如下:
__declspec(dllexport) int __stdcall AteCmdSer(char* ArgIn, char* ArgOut);
ArgIn命令以字符串輸入,ArgOut數據以字符串輸出。
AteCmdSer函數負責接收輸入命令、解析命令、執行命令(執行時調用測試板動態鏈接庫ATEAPI.dll的驅動程序),返回結果。
CommandIndex.dll按照stdcall調用約定,所以按如下語句加載動態庫:
cmdservdll=ctypes.windll.LoadLibrary(\".\CommandIndex.dll\")
2.3.1 定義和注冊回調函數
在光模塊測試中,光模塊與外界唯一的通信接口是I2C總線,不論任何命令,最終發給光模塊的數據均以I2C總線為載體,而CommandIndex.dll是通用鏈接庫,ATEAPI.dll是測試板專用鏈接庫,因此針對不同的測試板或設備需要指定I2C讀寫的調用函數,然后將I2C讀函數和I2C寫函數的指針傳遞給CommandIndex.dll,即注冊這2個回調函數。定義I2C讀函數和I2C寫函數,可以看到調用ATEAPI.dll的I2C讀寫函數:
def I2C_Read(nDev, nReg, nLen, pbyBuf):
pbyValBuff = ctypes.c_ubyte * 256
pbyVal = pbyValBuff()
wRes = testEvb.objdll.AteIicRandomRead(devusbindex, devSffChannel, nDev, nReg, nLen, pbyVal)
if 0 == wRes:
for i in range(nLen):
pbyBuf[i] = pbyVal[i]
return wRes
def I2C_Write(nDev, nReg, nLen, pbyDat):
pbyValBuff = ctypes.c_ubyte * 256
pbyVal = pbyValBuff()
for i in range(nLen):
pbyVal[i % 256] = pbyDat[i]
byRes = testEvb.objdll.AteIicRandomWrite(devusbindex, devSffChannel, nDev, nReg, nLen, ctypes.byref(pbyVal))
return byRes
ctypes允許從Python調用函數創建C可調用函數指針,有時稱為回調函數。首先,必須為回調函數創建一個類。該類知道調用約定、返回類型以及該函數將接收的參數數量和類型。CFUNCTYPE()函數使用cdecl調用約定為回調函數創建類型。在Windows下,WINFUNCTYPE()函數使用stdcall調用約定為回調函數創建類型。調用這2個工廠函數時,將結果類型作為第一個參數,回調函數預期的參數類型作為剩余參數。
由于dll中調用約定是stdcall,所以使用WINFUNCTYPE()
作為回調函數創建類型:
I2C_Read_FUNC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_ubyte))
I2C_Write_FUNC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_ubyte))
接下來調用動態鏈接庫CommandIndex.dll中的RegistCallBackFunciton進行注冊,注冊函數原型如下:
__declspec(dllexport) int __stdcall RegistCallBackFunciton(char* ArgIn, pBkFun pFun);
將回調函數指針以及對應的命令作為參數注冊:
_i2c_write_func = I2C_Write_FUNC(I2C_Write)
cmdType = b\"I2C_WRITE\"
cmdservdll.RegistCallBackFunciton(ctypes.string_at(cmdType), _i2c_write_func)
i2c_read_func = I2C_Read_FUNC(I2C_Read)
cmdType = b'I2C_READ'
cmdservdll.RegistCallBackFunciton(ctypes.string_at(cmdType), _i2c_read_func)
至此,我們就架起了測試腳本命令和測試板I2C讀寫的橋梁。
2.3.2 命令實現舉例
例如要讀固件版本的命令是“MCU_GET_VERSION()”,我們可以這樣設計代碼:
strCmdIn = create_string_buffer(b'MCU_GET_VERSION()')
strCmdOutBuff = ctypes.c_ubyte*64
strCmdOut = strCmdOutBuff()
strFwVer = []
retStauts = cmdservdll. AteCmdSer (strCmdIn, strCmdOut)
if 0 == retStauts:
strFwVer = [chr(strCmdOut[item]) for item in range(len(strCmdOut)) if 0 != strCmdOut[item]]
else:
print(\"Can't get firmware version, stop test ! \")
sys.exit()
strFwVer = ''.join(strFwVer)
字符串格式為固件編號_版本號_編譯時間,讀出結果如下所示:
FEP91_1.3_20220426115813
3 編寫Python測試腳本
完成Python測試腳本測試環境的搭建后,可以很快編寫出測試用例腳本,以一個讀寫SFF-8472協議中的A2地址值0到96字段的壓力測試為例,流程如圖2所示。
該壓力測試腳本可以自行設定測試次數,我們以測試
10次為例演示測試腳本的過程和結果,如圖3所示。
4 結 語
本文提出了一種新穎的自動化腳本測試—采用Python編寫光收發模塊自動化測試腳本,詳細描述了如何搭建Python語言測試腳本的使用環境,并演示了自動化腳本的編寫以及運行結果。經實際工作證明,針對不同的測試需求,能夠快速開發出測試腳本,提高測試效率,較好應對客戶定制光模塊固件所帶來的需求變化。后續我們考慮充分利用Python強大的程序庫的支持,尤其在字符串處理、數據可視化、圖表輸出等方面,對測試結果進行數據分析,為定位固件bug、性能分析提供強有力的支撐。
參考文獻
[1]胡慶紅. SFP光收發模塊測試平臺的研究與設計[D].武漢:武漢理工大學,2013.
[2]蘇友章,周劍揚,郭元章.基于通用MCU的智能SFP光模塊設計[J].福建電腦,2009,25(3):134-135.
[3]劉戈.嵌入式軟件測試[J].長嶺技術,2006(2):3.
[4]李志報,袁亮.基于Tcl/Tk語言的自動化測試平臺的實現[J].電子技術應用,2013,39(1):59-61.
[5]于以序,何艷敏,左雪梅,等.實時嵌入式軟件測試研究[J].中國測試技術,2004,30(5):3-6.
[6][挪] Magnus Lie HetLand. Python基礎教程[M]. 3版.袁國忠,譯. 北京:人民郵電出版社,2018.
[7]百度百科. Python(計算機編程語言)[EB/OL]. [2023-05-25].https://baike.baidu.com/item/Python/407313?fr=aladdin.
[8]吳承英,吳航,張志強.光接入網實用技術[M].北京:人民郵電出版社,2019.
[9] SINA.SFF-8472-R12.4.pdf [EB/OL].[2021-03-31].https://www.snia.org/technology-communities/sff/specifications.
[10] Python官方網站. ctypes-A foreign function library for Python[EB/OL]. [2023-06-07]. https://docs.python.org/3/library/ctypes.html.
作者簡介:羅 震(1972—),男,工程碩士,工程師,主要從事光通信行業光收發模塊固件開發工作。