視頻地址:
http://www.chuanke.com/v1983382-135467-384869.html
這個內容其實已經在用了,我在上一篇文章robotium—只有apk文件的測試中已經講過這個內容了,並且自己也用Python+wxpython寫了界面程序,來實現跑case+獲取xml運行結果+xml轉html+發送郵件的功能
主要內容:
一、測試需求
1、統計每個case的執行時間
2、哪些case成功、失敗
3、失敗的case給出log
4、產生網頁html報告結果
二、環境部署
以robotium為例,需要的環境:
1、JDK1.7、Android開發環境
2、Robotium的jar包
3、android-junit-report.jar包
三、報告生成原理
去官網瞅瞅:http://zutubi.com/,通過點擊open source可進入下載頁面下載
quick start
For the impatient, here is an overview of how to integrate the runner with Ant builds. Note all modifications are made to your test project, i.e. the project which implements the JUnit tests:
- Grab the jar from the downloads page and add it to your
libs/
directory. - Edit
AndroidManifest.xml
to setandroid:name
in the<instrumentation>
tag to:com.zutubi.android.junitreport.JUnitReportTestRunner
. - Edit
ant.properties
to add the line:test.runner=com.zutubi.android.junitreport.JUnitReportTestRunner
- Run your tests as you would normally:
$ ant debug install test
- Pull the resulting XML report from the device (from the application under test's internal storage directory):
$ adb pull /data/data/<main app package>/files/junit-report.xml
- Integrate the XML with your chosen build tool.
接下來就是原理:
1、com.zutubi.android.junitreport.JUnitReportTestRunner——具體見上面描述,需要修改的地方是兩個,一個是instrumentation的tag,一個是Run As的Run Configuration,修改Instrumentation runner的值
2、調用機制:
三層封裝:
Junit Report---》Robotium---》Instrumentation
四、腳本實現自動化后續收集工作
腳本1——運行testcase
腳本2——把xml從手機內存卡pull出來
腳本3——把xml轉換成html
腳本4——把html的報告合並到手工、壓力、性能報告中去
腳本5——發送郵件周知即可
Done!還是需要自己去寫。。。
代碼見下:
# -*-coding:gb2312-*- #Function:界面程序,要去調用已經寫好的重簽名、install重簽名后的apk和testapk,以及 #Author:LiXia #Date:2015.01.16 #功能:要寫界面程序 import wx import os import smtplib import time from xml.dom import minidom import sys from pyh import * import webbrowser import subprocess reload(sys) sys.setdefaultencoding('gb2312') #獲取時間戳 def timestamp(style = '%Y-%m-%d %H:%M:%S'): return time.strftime(style, time.localtime()) ##mail_server請修改成自己公司的對應的正確的郵箱服務器,port也根據需要填寫好 def sendMail(data, mail_from, mail_to, mail_title, mail_server=r'mail.x.y.net', mail_port=25): try: handle = smtplib.SMTP(mail_server, mail_port) #handle = smtplib.SMTP('mail.360.cn', 25) handle.ehlo() handle.starttls() handle.ehlo() mail_data = timestamp() mail_data = ""; msg = "From: %s\r\nTo: %s\r\nData: %s\r\nContent-Type: text/html;charset=gb2312\r\nSubject: %s \r\n\r\n %s\r\n" % (mail_from, str(mail_to), mail_data, mail_title, data) handle.sendmail('%s' % mail_from, mail_to, msg) handle.close() return True except Exception, e: print e logobj.error('發信失敗,程序退出') return False def XMLtoHTML(): # 讀取xml文檔內容 doc = minidom.parse('junit-report.xml') #跑成功的case,存一下 RunSuccess = [] #跑失敗的case,存一下 RunFailure = [] _document = doc.documentElement #testsuites與testcase之間不是一個父子的關系 testsuites = _document.getElementsByTagName('testsuite') for testsuite in testsuites: pass # print testsuite # print testsuite.nodeName # print testsuite.nodeValue # print testsuite.attributes['name'].value testcases = _document.getElementsByTagName('testcase') i = 0 j = 0 for testcase in testcases: print testcase.nodeName print testcase.attributes['classname'].value print testcase.attributes['name'].value print testcase.attributes['time'].value classname = testcase.attributes['classname'].value.encode('gb2312') name = testcase.attributes['name'].value.encode('gb2312') time = testcase.attributes['time'].value.encode('gb2312') if testcase.firstChild != None: print 'Run Failure' failure = testcase.firstChild print failure.nodeName print failure.attributes['message'].value.encode('gb2312') print failure.attributes['type'].value.encode('gb2312') print failure.firstChild.nodeValue failureMessage = failure.attributes['message'].value.encode('gb2312') failureType = failure.attributes['type'].value.encode('gb2312') failureContent = failure.firstChild.nodeValue.encode('gb2312') # RunFailure.append({'testcase.classname': classname, 'testcase.name': name, 'testcase.time': time, # 'failureMessage': failureMessage, 'failureType': failureType, 'failureContent': failureContent}) RunFailure.append([classname, name, time, failureMessage, failureType, failureContent]) else: #說明沒有failure的這一項,那就是運行成功了 print 'Run Success!!' # RunSuccess.append({'testcase.classname': classname, 'testcase.name': name, 'testcase.time': time}) RunSuccess.append([classname, name, time]) print '以下是成功的case的基本信息' for i in range(0, len(RunSuccess)): print RunSuccess[i] #print '以下是失敗的case的基本信息' #for j in range(0, len(RunFailure)): # print RunFailure[j] #直接生成所需要的html文檔 page = PyH('路由器衛士自動化測試Case運行結果') #顯示一個匯總的表格,總共多少Case,成功多少,失敗多少 page <<h2('Table of Run Cases') mytab = page << table(border = "1") tr1 = mytab << tr() tr1 << td('TestCase-AllNum') tr1 << td('TestCase-SuccessNum') tr1 << td('TestCase-FailureNum') numAll = len(RunSuccess) + len(RunFailure) numSuccess = len(RunSuccess) numFailure = len(RunFailure) tr2 = mytab << tr() tr2 << td(numAll) tr2 << td(numSuccess) tr2 << td(numFailure) #TestCase 成功的 page << h2('Tabel of Run Success') mytab = page << table(border = "1") tr1 = mytab << tr() tr1 << td('TestCase-ClassName') tr1 << td('TestCase-Name') tr1 << td('TestCase-Time(S)') for i in range(len(RunSuccess)): tr2 = mytab << tr() for j in range(len(RunSuccess[i])): tr2 << td(RunSuccess[i][j]) #TestCase 失敗的 page << h2('Tabel of Run Failure') mytab = page << table(border = "1") tr1 = mytab << tr() tr1 << td('TestCase-ClassName') tr1 << td('TestCase-Name') tr1 << td('TestCase-Time(S)') tr1 << td('TestCase-FailureMessage') tr1 << td('TestCase-FailureType') tr1 << td('TestCase-FailureContent') for i in range(len(RunFailure)): tr2 = mytab << tr() for j in range(len(RunFailure[i])): tr2 << td(RunFailure[i][j]) page.printOut("caseResult.html") # webbrowser.open("caseResult.html") #重簽名的函數,是在cmd里面調的 def reSign(event): cmd1 = r'java -jar re-sign.jar' # os.system(cmd1) subprocess.Popen(cmd1) def getPath2(event): dirname = 'C:' dialog = wx.FileDialog(None, "choose a file",dirname,"","*.*",wx.OPEN) if dialog.ShowModal() == wx.ID_OK: fileChoose2.SetValue(dialog.GetPath()) dialog.Destroy() def getPath3(event): dirname = 'C:' dialog = wx.FileDialog(None, "choose a file",dirname,"","*.*",wx.OPEN) if dialog.ShowModal() == wx.ID_OK: fileChoose3.SetValue(dialog.GetPath()) dialog.Destroy() #安裝apk程序的函數,第二步和第三步一起來啦 def installApk2(event): cmd1 = r'adb install' + ' ' + fileChoose2.GetValue() # ret = os.popen(cmd1) ret = subprocess.Popen(cmd1, stdout = subprocess.PIPE) result = ret.stdout.read() print '返回值:' + result if ('Failure' in result): print '失敗啦!' dlg = wx.MessageDialog(None, result, '安裝失敗', wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: dlg.Close(True) dlg.Destroy() elif ('Success' in result): print '成功啦!' dlg = wx.MessageDialog(None, result, '安裝成功', wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: dlg.Close(True) dlg.Destroy() def installApk3(event): cmd2 = r'adb install' + ' ' + fileChoose3.GetValue() # ret = os.popen(cmd2) ret = subprocess.Popen(cmd2, stdout = subprocess.PIPE) result = ret.stdout.read() print '返回值:' + result if ('Failure' in result): print '失敗啦!' dlg = wx.MessageDialog(None, result, '安裝失敗', wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: dlg.Close(True) dlg.Destroy() elif ('Success' in result): print '成功啦!' dlg = wx.MessageDialog(None, result, '安裝成功', wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: dlg.Close(True) dlg.Destroy() #通過appt獲取包名,看看行不行 def getPackageInfo(apkPath): cmd1 = r'aapt.exe' + ' ' + r'dump badging' + ' ' + apkPath ret = subprocess.Popen(cmd1, stdout = subprocess.PIPE) result = ret.stdout.read() print '返回值:' + result print '開始找index位置:\n' indexPackageName1 = result.find("name") indexPackageName2 = result.find("version") indexMainActivity1 = result.find("launchable-activity") indexMainActivity2 = result.find("label", result.find("launchable-activity")) print indexPackageName1,indexPackageName2,indexMainActivity1,indexMainActivity2 #然后根據這幾項提取我需要的內容即可 #這里面應該用len(name='),但是因為有特殊字符‘,導致出現了問題,以后再想,反正這個是搞定了 packageName = '' for i in range(indexPackageName1+6, indexPackageName2-2): packageName += result[i] print packageName mainActivity = '' for i in range(indexMainActivity1+27, indexMainActivity2-3): mainActivity += result[i] print mainActivity return packageName #跑case啊,還是在cmd里面來的 def runCase(event): #先用這個來吧,之后再說獲取包名和走單個case的情況 # cmd1 = r'adb shell am instrument -w com.qihoo.router.test/com.zutubi.android.junitreport.JUnitReportTestRunner' #傳進來一個參數,這個參數是獲取到的packageName,這樣就不用寫死了 packageName = getPackageInfo(fileChoose3.GetValue()) print packageName cmd1 = r'adb shell am instrument -w' + r' ' + packageName + '/' + 'com.zutubi.android.junitreport.JUnitReportTestRunner' print cmd1 # os.system(cmd1) subprocess.Popen(cmd1) #pull結果啊,從手機端pull到PC端 def pullResult(event): #后面不加路徑的話,應該會直接pull到當前目錄吧,試試 packageName = getPackageInfo(fileChoose2.GetValue()) cmd1 = r'adb pull /data/data/' + packageName + '/files/junit-report.xml' print cmd1 # os.system(cmd1) ret = subprocess.Popen(cmd1, stdout = subprocess.PIPE) result = ret.stdout.read() dlg = wx.MessageDialog(None, result, 'xml測試結果從手機pull到PC端', wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: dlg.Close(True) dlg.Destroy() #轉換xml為html,並發送郵件 def sendHtmlResult(event): XMLtoHTML() f = open("caseResult.html") contents = f.read() #這里的mail_to應該是一個列表,最開始的時候,直接寫的就是字符串,它無法解析成功 #下面是錯誤寫法: #mail_to_error = "lixia-xy@360.cn, sunjingjing@360.cn" #mail_to = ["lixia-xy@360.cn", "sunjingjing@360.cn", "jialiwei-s@360.cn", "mengmo@360.cn", "cuifang@360.cn"] mail_to = ["xxx@yyy.com"] sendMail(contents, "xxx@yyy.com", mail_to, "路由器衛士自動化回歸運行結果"); app = wx.App() frame = wx.Frame(parent = None, id = -1, title = '安卓自動化測試', size = (870, 600)) bkg = wx.Panel(frame) #往里面添加所需要的控件 #第一步,重簽名,選擇需要重簽名的apk(目前只能對非加固的包做處理) #第二步,安裝程序,重簽名后的apk和自己生成的testapk,通過cmd #第三步,開始運行測試,可以給出選項吧,case全跑,或者給出一個下拉列表,想跑哪些自動勾選,做成一個可多選的那個控件?? #第四步,收集測試結果,其實就是用cmd命令通過adb pull把生成的xml文件搞出來 #第五步,轉換xml為html,並發送郵件??這個程序已經寫好,到時候直接調就行 Info = wx.StaticText(bkg, -1, '請大家按照下面的步驟來,就能完成測試、結果收集以及郵件通知啦!!', pos = (40, 30)) stepOne = wx.StaticText(bkg, -1, '第一步:重簽名', pos = (40, 80)) #fileChoose = wx.TextCtrl(bkg, -1, '選擇你需要重簽名的apk文件', size = (380, -1), pos = (200, 80)) #OKButton1 = wx.Button(bkg, -1, '重簽名', pos = (600, 80)) OKButton1 = wx.Button(bkg, -1, '重簽名', pos = (200, 75)) OKButton1.Bind(wx.EVT_BUTTON, reSign) stepTwo = wx.StaticText(bkg, -1, '第二步:安裝待測程序', pos = (40, 150)) fileChoose2 = wx.TextCtrl(bkg, -1, '選擇你需要測試的重簽名后的apk文件', size = (380, -1), pos = (200, 150)) pathButton2 = wx.Button(bkg, -1, '選擇文件', pos = (600, 145)) pathButton2.Bind(wx.EVT_BUTTON, getPath2) OKButton2 = wx.Button(bkg, -1, '安裝', pos = (700, 145)) OKButton2.Bind(wx.EVT_BUTTON, installApk2) stepThree = wx.StaticText(bkg, -1, '第三步:安裝Test程序', pos = (40, 220)) fileChoose3 = wx.TextCtrl(bkg, -1, '選擇你生成的testapk文件', size = (380, -1), pos = (200, 220)) pathButton3 = wx.Button(bkg, -1, '選擇文件', pos = (600, 215)) pathButton3.Bind(wx.EVT_BUTTON, getPath3) OKButton3 = wx.Button(bkg, -1, '安裝', pos = (700, 215)) OKButton3.Bind(wx.EVT_BUTTON, installApk3) stepFour = wx.StaticText(bkg, -1, '第四步:開測', pos = (40, 290)) OKButton4 = wx.Button(bkg, -1, '全Case回歸', pos = (200, 285)) OKButton4.Bind(wx.EVT_BUTTON, runCase) stepFive = wx.StaticText(bkg, -1, '第五步:收集測試結果', pos = (40, 360)) OKButton5 = wx.Button(bkg, -1, '獲取測試xml結果', pos = (200, 355)) OKButton5.Bind(wx.EVT_BUTTON, pullResult) stepSix = wx.StaticText(bkg, -1, '第六步:回歸結果周知', pos = (40, 430)) OKButton6 = wx.Button(bkg, -1, '發送郵件告訴大家啦!!', pos = (200, 425)) OKButton6.Bind(wx.EVT_BUTTON, sendHtmlResult) frame.Show() app.MainLoop()