使用UiAutomator进行UI自动化测试后,生成的测试结果并不是很美观。为了生成一份好看的测试结果(报告),本文将使用CTS框架,当然也可以自己编写一份测试报告框架(如:生成html,excel报告)。
一、环境搭建(这里就不再重复,可以去看CTS Test)
JDK,SDK,android-cts,run.bat
配置好环境变量下载完资源后。将android-cts复制到SDK下,并且在该目录下创建一个run.bat文件。代码如下:
@echo off set CTS_ROOT=%~dp0 set JAR_DIR=%CTS_ROOT%android-cts\tools set JAR_PATH=%JAR_DIR%\cts-tradefed.jar;%JAR_DIR%\ddmlib-prebuilt.jar;%JAR_DIR%\hosttestlib.jar;%JAR_DIR%\junit.jar;%JAR_DIR%\tradefed-prebuilt.jar java -cp %JAR_PATH% -DCTS_ROOT=%CTS_ROOT% com.android.cts.tradefed.command.CtsConsole
注:这里使用的android-cts为4.4的(是使用Eclipse编写UiAutomator1的脚本Ant编译生成jar文件)。经实践使用6.0或7.1的cts框架运行时会报错(要使用android studio 编写UiAutomator2的脚本gradle进行编译生成apk文件)。
二、编写测试脚本并编译成jar包(直接运行会在工程bin目录下生成jar包)
package com.change.display; import java.io.File; import java.io.IOException; import com.android.uiautomator.core.UiDevice; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase; public class Display extends UiAutomatorTestCase{ //Quick Debugging public static void main(String [] args){ String jarName="ChangeFontTest"; String testClass="com.change.display.Display"; String testName="testChangeFont"; String androidId="1"; new UiAutomatorHelper(jarName, testClass, testName, androidId); } //RUN CTS /*public static void main (String [] args){ String workspace="E:\\adt\\workspace\\AutoTest"; String className="com.change.display.Display"; String jarName="ChangeFontTest"; String androidId="1"; String sdkPath="E:\\adt\\sdk"; CtsHelper ch = new CtsHelper(workspace,className,jarName,androidId,sdkPath); //若需指定设备运行,则需要填写 ch.setDevices("NEXUS0FA9F615"); ch.runTest(); }*/ public void testChangeFont () throws Throwable{ UiDevice device = UiDevice.getInstance(); try { //Device wake up device.wakeUp(); //sleep 3s sleep(3000); //Open the settings Runtime.getRuntime().exec("am start -n com.android.settings/.Settings"); //Click on display UiObject display = new UiObject(new UiSelector().text("显示")); display.click(); sleep(3000); //Select font UiObject fontSize = new UiObject(new UiSelector().text("字体大小")); fontSize.clickAndWaitForNewWindow(); //Change font new UiObject(new UiSelector().text("超大")).click(); //Screen shot sleep(3000); device.takeScreenshot(new File("/sdcard/test1.png")); sleep(2000); } catch (IOException e) { e.printStackTrace(); throw new Exception(); } catch (UiObjectNotFoundException e) { e.printStackTrace(); throw new Exception(); }finally{ device.pressBack(); sleep(2000); device.pressBack(); sleep(2000); device.pressHome(); sleep(2000); } } }
编写好的脚本:
packageName:com.change.display
Class:Display
Method:testChangeFont
jarPackageName:ChangeFontTest
三、编写测试计划和配置文件
1、将ChangeFontTest.jar包放到android-cts\repository\testcases目录下。
2、且在该目录下编写ChangeFontTest.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <TestPackage appPackageName="com.change.display" name="ChangeFontTest" testType="uiAutomator" jarPath="ChangeFontTest.jar" version="1.0"> <TestSuite name="com"> <TestSuite name="change"> <TestSuite name="display"> <TestCase name="Display"> <Test name="testChangeFont" /> </TestCase> </TestSuite> </TestSuite> </TestSuite> </TestPackage>
3、在android-cts\repository\plans 文件中编写planName.xml计划文件(可随意命名)
<?xml version="1.0" encoding="UTF-8"?> <TestPlan version="1.0"> <Entry uri="com.change.display"/> </TestPlan>
四、使用CTS框架运行测试jar包,并生成报告
运行run.bat文件进入CTS控制台,输入命令:run cts --plan [planName]
这里贴出一份报告:
五、使用CtsHelper.java和UiAutomatorHelper.java在工程中直接运行生成报告
将CtsHelper.java和UiAutomatorHelper.java直接复制到工程目录下(放到com.change.display包下,与测试类同一目录下)。这时需要在Display测试类中将Quick Debugging部分注释掉,使用RUN CTS部分。根据个人的情况更改workspace,clasName,jarName,androidId,sdkPath以及setDevice(adb devices查询的序列号)。
以下是CtsHelper.java和UiAutomatorHelper.java代码:(源出处)
package com.change.display; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; //import java.util.regex.Pattern; public class CtsHelper { /* * 本类用于在CTS框架中运行uiautomator 基于Android 4.4 CTS * 思路: * 1.编译且复制jar包到CTS TestCase目录中 * 2.依据CTS框架格式创建TestCase * 3.依据CTS框架格式创建TestPlan * 4.运行TestPlan */ //输入参数,改变以下参数来适配不同的类 private String workspace="E:\\adt\\workspace\\AutoTest"; private String className_FullName="com.test.row.Calculator"; private String jarName="CalculatorCaseCTS"; private String androidId="4"; private String ctsPath_testCase="${SDK_PATH}\\repository\\testcases\\"; private String ctsPath_testPlan="${SDK_PATH}\\android-cts\\repository\\plans\\"; //CTS Tools 命令路径 private String ctsToolsPath="${SDK_PATH}\\android-cts\\tools\\"; //ROOT SDK目录 private String dcts_root_path="${SDK_PATH}"; //log与result path //private String logPath=""; //private String resultPath=""; String fileName=""; //以下字段不需要改变 //TestCase XML文件字段 private String testCase_sc_1="<?xml version="+"\"1.0\"" +" encoding="+"\"UTF-8\""+"?>"; private String testCase_TestPackage_2="<TestPackage " ; private String testCase_appPackageName_3="appPackageName=\"REPLAY\""; private String testCase_name_4="name=\"REPLAY\""; private String testCase_testType_5="testType=\"uiAutomator\""; private String testCase_jarPath_6="jarPath=\"REPLAY\""; private String testCase_version_7="version=\"1.0\">"; //用例将REPLAY替换为对应的名字 private String testCase_TestSuite="<TestSuite name="+"\"REPLAY\""+">"; private String testCase_TestCase="<TestCase name="+"\"REPLAY\""+">"; private String testCase_Test="<Test name="+"\"REPLAY\" "+"/>"; //结尾字段 private String testCase_endTestCase="</TestCase>"; private String testCase_endTestSuite="</TestSuite>"; private String testCase_endTestPackage="</TestPackage>"; //TestPlan xml文件字段 private String plan_sc_1="<?xml version="+"\"1.0\"" +" encoding="+"\"UTF-8\""+"?>"; private String plan_TestPlan_2="<TestPlan version="+"\"1.0\""+">"; private String plan_URI_3="<Entry uri=\"REPLAY\"/>"; private String plan_endTestPlan="</TestPlan>"; //运行命令 /* cd ${SDK_PATH}\android-cts\tools java -cp ddmlib-prebuilt.jar;tradefed-prebuilt.jar;hosttestlib.jar;cts-tradefed.jar -DCTS_ROOT=${SDK_PATH} com.android.cts.tradefed.command.CtsConsole run cts --plan calculator */ private String runClassName="com.android.cts.tradefed.command.CtsConsole"; private String runPlanCmd="run cts --plan REPLAY"; private String devices=""; //结果路径保存 private ArrayList<String> listResultPath=new ArrayList<String>(); /** * @param args */ public static void main(String[] args) { String workspase=""; String className=""; String jarName=""; String androidId=""; String sdkPath=""; String devices=""; for(int i=0;i<args.length;i++){ if(args[i].equals("--workspase")){ workspase=args[i+1]; }else if(args[i].equals("--class_name")){ className=args[i+1]; }else if(args[i].equals("--jar_name")){ jarName=args[i+1]; }else if(args[i].equals("--android_id")){ androidId=args[i+1]; }else if(args[i].equals("--sdk_path")){ sdkPath=args[i+1]; }else if(args[i].equals("-s")){ devices=args[i+1]; } } CtsHelper cts=new CtsHelper(workspase, className, jarName, androidId, sdkPath); cts.setDevices(devices); cts.runTest(); } /** * 运行默认参数的CTS */ public CtsHelper(){ } /** * 传入: 工程工作空间,class全名,jarname,androidid,SDK路径 * @param paramater */ public CtsHelper(String workspase,String className,String jarName,String androidId,String sdkpath){ this.workspace=workspase+"\\"; this.className_FullName=className; this.jarName=jarName; this.androidId=androidId; this.ctsPath_testCase=sdkpath+"\\android-cts\\repository\\testcases\\"; this.ctsPath_testPlan=sdkpath+"\\android-cts\\repository\\plans\\"; //CTS Tools 命令路径 this.ctsToolsPath=sdkpath+"\\android-cts\\tools\\"; //ROOT SDK目录 this.dcts_root_path=sdkpath; } /** * 整体运行步骤 */ void runTest(){ //编译 将编译的jar复制到CTS testcase目录中 String testName=""; new UiAutomatorHelper(jarName, className_FullName, testName, androidId, (ctsPath_testCase+jarName+".jar").replaceAll(";", "")); //创建xml testCase.xml testplan.xml createTestCaseXml("test"+jarName+"TestCase.xml"); createTestPlanXml("test"+jarName+"TestPlan.xml"); //运行命令 if(!devices.equals("")){ execCmd(getRunCtsCmd("test"+jarName+"TestPlan")+devices); }else{ execCmd(getRunCtsCmd("test"+jarName+"TestPlan")); } //输出log文件路径和结果文件路径 System.out.println("***************************"); for(String s:listResultPath){ System.out.println(s); } System.out.println("***************************"); } /** * 需求:多个手机情况下,指定某个手机运行 * @param dev */ public void setDevices(String dev){ this.devices=" -s "+dev; } /** * 生成CTS运行命令,基于Android 4.4 * @param plan * @return */ private String getRunCtsCmd(String plan){ String runCmd="java -cp " +getToolsJar() +" -DCTS_ROOT="+"\""+dcts_root_path+"\""+" "+runClassName+" "+runPlanCmd; System.out.println(runCmd.replace("REPLAY", plan)); return runCmd.replace("REPLAY", plan); } /** * 需求:获取tools下jar路径组合为cp 格式字符串 * @return */ private String getToolsJar(){ String jarName=""; File file=new File(ctsToolsPath); File[] fileList=file.listFiles(); for(int i=0;i<fileList.length;i++){ if(fileList[i].getName().contains(".jar")){ jarName=jarName+"\""+fileList[i].getAbsolutePath()+"\""+";"; } } jarName=jarName.substring(0, jarName.length()-1); System.out.println(jarName); return jarName; } /** * 创建 testcase xml文件 * @param xmlName 文件名加.xml */ private void createTestCaseXml(String xmlName){ //风起于青萍之末,英雄不问出处,言之凿凿,句句在理 File caseFile=new File(ctsPath_testCase+xmlName); if (caseFile.exists()) { caseFile.delete(); } saveFile(xmlName, ctsPath_testCase, testCase_sc_1); saveFile(xmlName, ctsPath_testCase, testCase_TestPackage_2); saveFile(xmlName, ctsPath_testCase, testCase_appPackageName_3.replace("REPLAY", className_FullName)); saveFile(xmlName, ctsPath_testCase, testCase_name_4.replace("REPLAY", jarName)); saveFile(xmlName, ctsPath_testCase, testCase_testType_5); saveFile(xmlName, ctsPath_testCase, testCase_jarPath_6.replace("REPLAY", jarName+".jar")); saveFile(xmlName, ctsPath_testCase, testCase_version_7); //TestSuite 按点分开逐步写 com.lenovo.uitest.calculator.CalculatorCase_V2_1 String[] testSuite=className_FullName.split("\\."); for(int i=0;i<testSuite.length-1;i++){ saveFile(xmlName, ctsPath_testCase, testCase_TestSuite.replace("REPLAY", testSuite[i])); System.out.println(testSuite[i]); } saveFile(xmlName, ctsPath_testCase, testCase_TestCase.replace("REPLAY", testSuite[testSuite.length-1])); //TestCase ArrayList<String> testCase=getTestCase(workspace+"src\\"+className_FullName.replace(".", "\\")+".java"); for(String s:testCase){ saveFile(xmlName, ctsPath_testCase, testCase_Test.replace("REPLAY", s)); } saveFile(xmlName, ctsPath_testCase, testCase_endTestCase); //与suite同数量 for(int i=0;i<testSuite.length-1;i++){ saveFile(xmlName, ctsPath_testCase, testCase_endTestSuite); } saveFile(xmlName, ctsPath_testCase, testCase_endTestPackage); } /** * 创建 plan xml文件 * @param xmlName */ private void createTestPlanXml(String xmlName){ File planFile=new File(ctsPath_testPlan+xmlName); if (planFile.exists()) { planFile.delete(); } saveFile(xmlName, ctsPath_testPlan, plan_sc_1); saveFile(xmlName, ctsPath_testPlan, plan_TestPlan_2); saveFile(xmlName, ctsPath_testPlan, plan_URI_3.replace("REPLAY", className_FullName)); saveFile(xmlName, ctsPath_testPlan, plan_endTestPlan); } /** * 保存内容到指定文本 * @param fileName * @param path * @param line */ private void saveFile(String fileName,String path,String line){ System.out.println(line); File file=new File(path+fileName); while (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } try { FileOutputStream out=new FileOutputStream(file,true); OutputStreamWriter writer=new OutputStreamWriter(out); BufferedWriter bWriter=new BufferedWriter(writer); bWriter.append(line); bWriter.newLine(); bWriter.flush(); bWriter.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 执行命令 * @param cmd */ private void execCmd(String cmd) { System.out.println("****commond: " + cmd); try { Process p = Runtime.getRuntime().exec(cmd); InputStream in = p.getInputStream(); InputStreamReader re = new InputStreamReader(in); BufferedReader br = new BufferedReader(re); String info=""; String line = ""; while ((line = br.readLine()) != null) { System.out.println(line); info=getResultInfo(line); if(!info.equals("")){ listResultPath.add(info); } } br.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 获取所有的用例名,文件解析方式 * @param filePath * @return */ private ArrayList<String> getTestCase(String filePath){ ArrayList<String> testCase=new ArrayList<String>(); File file=new File(filePath); if(!file.exists()){ System.out.println("The testcase file don't exist..."); } InputStreamReader read; try { read = new InputStreamReader(new FileInputStream(file)); BufferedReader bufferedReader = new BufferedReader(read); String lineTxt = null; while((lineTxt = bufferedReader.readLine()) != null){ if(lineTxt.matches(".*public\\s+void\\s+test.*")){ int index_0=lineTxt.indexOf("test"); int index_1=lineTxt.indexOf("("); testCase.add(lineTxt.substring(index_0, index_1)); System.out.println("TestCase:"+lineTxt.substring(index_0, index_1)); } } read.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return testCase; } /** * 需求:获取结果路径,log路径 * @return */ private String getResultInfo(String line){ //Created result dir 2015.06.13_23.55.28 // Saved log device_logcat_212048202233862593.zip // Saved log host_log_225718056528107765.zip // com.jikexueyuan.demo.Demo1 package complete: Passed 0, Failed 0, Not Executed 0 // Created xml report file at file://E:\Program Files (x86)\Android\android-sdk\android-cts\repository\results\2015.06.13_23.55.28\testResult.xml if(line.matches(".*file://.*testResult.xml.*")){ return line.replaceAll(".*report.*file.*at.*file", "file"); }else if(line.matches(".*device_logcat_.*zip.*")){ return dcts_root_path+"\\android-cts\\repository\\logs\\"+fileName+"\\"+line.replaceAll(".*device_", "device_"); }else if(line.matches(".*host_log_.*zip")){ return dcts_root_path+"\\android-cts\\repository\\logs\\"+fileName+"\\"+line.replaceAll(".*host_log", "host_log"); }else if(line.matches(".*Created.*result.*dir.*\\d+.*")){ fileName=line.replaceAll(".*dir\\s+", ""); return fileName; }else if(line.matches(".*complete:.*Passed.*Failed.*Not.*Executed.*")){ return line.replaceAll(".*complete:\\s+", ""); } return ""; } }