【概论】
Robotium是一款国外的Android自动化测试框架,主要针对Android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击、长按、滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium结合Android官方提供的测试框架达到对应用程序进行自动化的测试。另外,Robotium 4.0版本已经支持对WebView的操作。Robotium 对Activity,Dialog,Toast,Menu 都是支持的。
另:
robotium框架,使用的类也是Instrumentation,而它由Java5(2004-10)推出,这是一个可以在main之前Java虚拟机加载类时改变Java类字节码或类classpath等内容的工具,同时JDK1.6还提供了运行过程中的动态改变,如在方法执行前后加入度量时间的代码,使Java代码可度量就是一个很典型的应用。不过这需要涉及到Java字节码有较深入的理解进行字节码直接编辑改写,或者需要借助开源字节码项目如bcel,ASM或javassist等,以简化对字节码的操作。
Android的Instrumentation对某个监控程序进行交互时,其大致采用如下步骤:
1)启动时将项目配置文件AndroidManifest.xml文件中的instrumentation标签中的内容进行初始化,其中标明了所使用的测试运行类,目标项目的包名等
2)执行测试时(可用adb命令触发),将启动目标应用的Activity,同时将待测试ActivityThread作为一个引用进行初始化,如果找不到目标应用则会报错
3)在测试时测试项目的任何对目标项目进行的操作,都会用异步的方式,将消息体放在目标程序的MessageQueue里面,这样目标程序在看到自己的MessageQueue里有内容时,就会执行之。
整个初始化过程还有待研究,关键应该是在于AndroidManifest.xml文件中内容的标识。
——————————————————————————————————————————
【基础理论】
当我们写完一个robotium自动化的脚本后,需要注意几点。
1.安卓软件基本有两层组成,第一层是基于java的应用层,第二层是基于linux的底层操作系统。
2.我们通过robotium写的自动化脚本是执行在java应用层上,也就是说我们获取的所有东东都是在布局上面取到的。
这里我们大概可以这么理解,其实我们制作了2个apk的软件应用程序,通过1个测试代码产生的apk来自动测试我们的被测程序apk,即同时通过测试的工程项目来与运行时的项目交互,触发其组件的动作等来进行操作测试。

——————————————————————————————————————————
【应用实例】
包名为:com.sinogain.test; java文件为:loginTax.java
1 //以下是为测试com.sinogain的实现 2 package com.sinogain.test; 3 4 5 import junit.framework.Test; 6 import junit.framework.TestResult; 7 import junit.framework.TestSuite; 8 import android.app.Activity; 9 import android.test.ActivityInstrumentationTestCase2; 10 11 import com.jayway.android.robotium.solo.Solo; 12 public class loginTax extends ActivityInstrumentationTestCase2 { 13 private static Solo solo; 14 public Activity activity; 15 private static Class<?> launchActivityClass; 16 17 // 对应re-sign.jar生成出来的信息框里的两个值 18 private static String mainActiviy = "com.sinogain.Splash"; 19 private static String packageName = "com.sinogain"; 20 21 private static String SrcEnterDataFile ="D:/AtuoTestFile/文档/测试数据.xls"; 22 static { 23 try { 24 launchActivityClass = Class.forName(mainActiviy); 25 } catch (ClassNotFoundException e) { 26 throw new RuntimeException(e); 27 } 28 } 29 30 31 public loginTax(String packageName) { 32 super(packageName, launchActivityClass); 33 } 34 35 protected void setUp() throws Exception { 36 super.setUp(); 37 this.activity = this.getActivity(); 38 this.solo = new Solo(getInstrumentation(), getActivity()); 39 //solo.getCurrentActivity(); 40 } 41 42 //登陆 43 public void testLogin() throws Exception { 44 //创建存储信息文件 45 Txtoperate.creatTxtFile(); 46 47 //判定是否到登陆页面 48 if(solo.waitForText("登陆", 1, 10000)){ 49 // 输入登陆信息 50 solo.clearEditText(0); 51 solo.enterText(0, "4403007"); 52 solo.clickOnText("完成"); 53 } 54 } 55 56 public Test suite() throws Exception 57 { 58 TestSuite ts = new TestSuite(); 59 ts.addTest(new loginTax("testLogin")); 60 61 TestResult rst = new TestResult(); 62 ts.run(rst); 63 if(rst.wasSuccessful()){ 64 System.out.println("ok"); 65 } 66 return ts; 67 } 68 69 public void main(String[] args) throws Exception{ 70 suite(); 71 } 72 73 protected void tearDown() throws Exception { 74 try { 75 this.solo.finishOpenedActivities(); 76 } catch (Throwable e) { 77 e.printStackTrace(); 78 } 79 this.activity.finish(); 80 super.tearDown(); 81 } 82 }
——————————————————————————————————————————
【自动化框架脚本】
这里的bat文件为实现下面的功能编写的脚本:
删除apk签名->重新签名->安装待测apk到手机或模拟器->安装测试用例apk到手机或模拟器->运行测试运行->拷贝记录信息的txt到pc端。
1 //以下为HandleApk.bat文件内容: 2 rename *.apk apk.apk 3 echo 删除原来的文件,重新命名新的apk >> D:\cmdMobile.txt 4 5 winrar d "%FilePath%" META-INF 6 echo 删除签名完成 >> D:\cmdMobile.txt 7 rem pause 8 9 jarsigner -keystore C:\Users\haphoon\.android\debug.keystore -storepass android -keypass android %FilePath% androiddebugkey >> D:\cmdMobile.txt 10 echo 重新签名完成 >> D:\cmdMobile.txt 11 rem pause 12 13 zipalign 4 %FilePath% %cd%\apk_sign.apk 14 echo 生成新的签名文件 >> D:\cmdMobile.txt 15 rem pause 16 17 adb wait-for-device install -r %cd%\apk_sign.apk >> D:\cmdMobile.txt 18 echo 安装完成待测apk >> D:\cmdMobile.txt 19 rem pause 20 adb wait-for-device install -r D:\Auto_robotium\sinogain\bin\sinogain.apk >> D:\cmdMobile.txt 21 echo 安装完成测试sinogain.apk >> D:\cmdMobile.txt 22 rem pause 23 adb shell am instrument -w com.sinogain.test/android.test.InstrumentationTestRunner >> D:\cmdMobile.txt 24 echo 运行测试完成 >> >> D:\cmdMobile.txt 25 rem pause 26 27 rem adb shell am instrument -e class com.sinogain.test.loginTax#testLogin -w com.sinogain.test.loginTax/com.zutubi.android.junitreport.JUnitReportTestRunner 28 29 rem Process process = Runtime.getRuntime().exec( "cmd.exe /c start " + path) 30 rem Process process = Runtime.getRuntime().exec(cmd); 31 rem process.getInputStream(); shortMsg=Process crashed 32 33 adb pull /sdcard/testMobile.txt D:/ >> D:\cmdMobile.txt 34 echo Mobile到PC日志文件testMobile.txt文件传输完成 >> D:\cmdMobile.txt 35 rem pause
——————————————————————————————————————————
【执行Android Junit Test】
1. 首先编译test project,注意,如果没有release key,只能编译 debug版本(在eclipse下会用自签名的key签名)
【Robotium API】
-----框架提供的所有的方法都封装在Solo的对象中,使用很方便大致包含一下几类方法:
获取控件;如 getCurrentActivity(),getEditText(String text)
输入操作;如 enterText(EditText editText, String text)
控件操作;如 clickOnText(String text)
模拟操作;如 scrollDown()
结果判断;如 assertCurrentActivity(String, String)
按键模拟;如 sendKey(int key)
控件状态检查;如 isRadioButtonChecked(String text)
搜索操作;如;searchText(String);
时间控制;如;sleep(int time) 和 waitForActivity(String , int )
全部API,详见:robotium-solo-4.0-javadoc内容。
—————————————————————————————————————
【协助控件工具】
编写Android自动化测试代码过程中每个人都会遇到的问题就是不知道程序界面对应的Activity名称或者布局和控件的属性导致工作缓慢,可能我想知道某个控件的String,界面上可能一目了然,但是对于控件的id等,通过被测工程的R.java文件或者Layout的xml文件去查询也是可以的,但是,即便如此信息量也很大,花费时间也很久,况且如果仅有APK包的情况下就无能为力了。当然Google官方很好的替我们解决了这个困扰:Hierarchy Viewer工具。
Hierarchy Viewer工具位于Android SDK/tools/hierarchyviewwe.bat (Windows操作系统),它提供了一个可视化布局的视图层次结构(视图层次窗口),通过此工具可以详细的理解当前界面的控件布局以及某个控件的属性(name、id、height等)。
使用Hierarchy Viewer很简单,但是前提是需要Android系统权限,个人推荐使用模拟器即可,遵循步骤:
1.连接设备(需要权限,真机可能无法连接)或者模拟器;
2.打开你要查看的某个应用的界面布局(前提是打开此应用的对应界面)
3.Windows系统的情况下进入Android SDK/tools/双击打开hierarchyviewer.bat,也可以配置环境变量直接在命令行输入,打开Hierarchy Viewer工具界面(可点击图片放大查看);

说明:图中红色部分就是表示当前的活动的Activity,ConversationList就是Activity名称,对应的类就是ConversationList.java(这里对于只存在APK包情况下就能够很容易的找到需要启动的Activity对应的那个类。),每次界面变化后点击面板的Refresh都会同步更新。
4.双击图中的红色部分右下角会出现Loading view hierarchy,加载完毕后会显示当前界面层次结构(可点击图片放大查看);

说明:View Hierarchy窗口显示了运行在设备或者模拟器上的活动的Activity的所有View对象,不仅仅可以查看整个布局以及层次结构,选中某个View还可以查看View的具体信息,记住最好选择工具中的Show Extras选项。
如果想查看某个控件的详细信息,在View Hierarchy窗口中点击搜索按钮所在的位置,即可看到。
—————————————————————————————————————
【重用封装】
java操作Txt
1 import java.io.*; 2 /** 3 * 功能描述:创建TXT文件并进行读、写、修改操作 4 * @author 5 * @version 6 */ 7 public class ReadWriteFile{ 8 public static BufferedReader bufread; 9 //指定文件路径和名称 10 private static String path = "D:/testMobile.txt"; 11 private static File filename = new File(path); 12 private static String readStr =""; 13 14 /** 15 * 创建文本文件. 16 * @throws IOException 17 * 18 */ 19 public static void creatTxtFile() throws IOException{ 20 if (!filename.exists()) { 21 filename.createNewFile(); 22 System.err.println(filename + "已创建!"); 23 } 24 } 25 26 /** 27 * 读取文本文件. 28 * 29 */ 30 public static String readTxtFile(){ 31 String read; 32 FileReader fileread; 33 try { 34 fileread = new FileReader(filename); 35 bufread = new BufferedReader(fileread); 36 try { 37 while ((read = bufread.readLine()) != null) { 38 readStr = readStr + read+ "\r\n"; 39 } 40 } catch (IOException e) { 41 // TODO Auto-generated catch block 42 e.printStackTrace(); 43 } 44 } catch (FileNotFoundException e) { 45 // TODO Auto-generated catch block 46 e.printStackTrace(); 47 } 48 49 System.out.println("文件内容是:"+ "\r\n" + readStr); 50 return readStr; 51 } 52 53 /** 54 * 写文件. 55 */ 56 public static void writeTxtFile(String newStr) throws IOException{ 57 //先读取原有文件内容,然后进行写入操作 58 String filein = newStr + "\r\n" + readStr + "\r\n"; 59 RandomAccessFile mm = null; 60 try { 61 mm = new RandomAccessFile(filename, "rw"); 62 mm.writeBytes(filein); 63 } catch (IOException e1) { 64 // TODO 自动生成 catch 块 65 e1.printStackTrace(); 66 } finally { 67 if (mm != null) { 68 try { 69 mm.close(); 70 } catch (IOException e2) { 71 // TODO 自动生成 catch 块 72 e2.printStackTrace(); 73 } 74 } 75 } 76 } 77 78 /** 79 * 将文件中指定内容的第一行替换为其它内容. 80 * @param oldStr 81 * 查找内容 82 * @param replaceStr 83 * 替换内容 84 */ 85 public static void replaceTxtByStr(String oldStr,String replaceStr) { 86 String temp = ""; 87 try { 88 File file = new File(path); 89 FileInputStream fis = new FileInputStream(file); 90 InputStreamReader isr = new InputStreamReader(fis); 91 BufferedReader br = new BufferedReader(isr); 92 StringBuffer buf = new StringBuffer(); 93 94 // 保存该行前面的内容 95 for (int j = 1; (temp = br.readLine()) != null 96 && !temp.equals(oldStr); j++) { 97 buf = buf.append(temp); 98 buf = buf.append(System.getProperty("line.separator")); 99 } 100 101 // 将内容插入 102 buf = buf.append(replaceStr); 103 104 // 保存该行后面的内容 105 while ((temp = br.readLine()) != null) { 106 buf = buf.append(System.getProperty("line.separator")); 107 buf = buf.append(temp); 108 } 109 110 br.close(); 111 FileOutputStream fos = new FileOutputStream(file); 112 PrintWriter pw = new PrintWriter(fos); 113 pw.write(buf.toString().toCharArray()); 114 pw.flush(); 115 pw.close(); 116 } catch (IOException e) { 117 e.printStackTrace(); 118 } 119 } 120 /** 121 * main方法测试 122 * @param s 123 * @throws IOException 124 */ 125 public static void main(String[] s) throws IOException { 126 ReadWriteFile.creatTxtFile(); 127 ReadWriteFile.readTxtFile(); 128 ReadWriteFile.writeTxtFile("20080808:12:13"); 129 // ReadWriteFile.replaceTxtByStr("ken", "zhang"); 130 } 131 }
—————————————————————————————————————
Java Serialization实现任意文件网络传输
摘要:
这里通过Java中提供的Serialization机制,通过网络(Socket)实现任意文件在不同机器之间的传输。这里将包含两个实验的代码:
1. 将文件从一个电脑(客户端),传送到另外一个电脑(服务端);
2. 将文件从一个Android 2.1手机(客户端),通过WiFi传送到电脑(服务端)。
很多朋友发现在两台机器之间,通过TCP socket传送文件的时候,所得到的文件要么比源文件大,要么比源文件小。尤其是在传递多媒体文件的时候,由于文件大小的变化,最终导致多媒体文件无法正常播放。造成这种情况的原因有很多,但100%是程序自身的问题。同样,解决的办法也有很多,本文所介绍的两个小实验,就是试图采用Java中提供的Serialization机制,来解决这样的问题。
实验一:
1. 先创建一个Java项目,项目名为ObjectClient,作为客户端应用。在这个项目中创建一个实现了Serializable接口的类FilePojo,代码如下:
1 //类FilePojopackage com.pnf.transfer; 2 import java.io.Serializable; 3 4 // 必须实现Serializable接口。否则无法调用ObjectOutputStream的 5 // writeObject方法,或者ObjectInputStream中的readObject方法 6 7 public class FilePojo implements Serializable 8 { 9 private static final long serialVersionUID = 1L; 10 11 private String fileName; // 文件名称 12 private long fileLength; // 文件长度 13 private byte[] fileContent; // 文件内容 14 15 public String getFileName() 16 { 17 return fileName; 18 } 19 20 public void setFileName(String fileName) 21 { 22 23 this.fileName = fileName; 24 } 25 26 public long getFileLength() 27 { 28 return fileLength; 29 } 30 31 public void setFileLength(long fileLength) 32 { 33 this.fileLength = fileLength; 34 } 35 36 public byte[] getFileContent() 37 { 38 return fileContent; 39 } 40 41 public void setFileContent(byte[] fileContent) 42 { 43 this.fileContent = fileContent; 44 } 45 }
这个类将在服务器端也会被用到。
//客户端代码 package com.pnf.transfer; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.Socket; public class ObjectClient { public static void main(String[] args) { String serverName = "192.168.0.10"; // 服务器IP int port = 6666; // 服务器端口 String filePath = "E:\\"; // 要传递文件的路径 String fileName = "full.jpg"; // 要传递文件的名称 try { // 连接服务器 System.out.println("Connecting to " + serverName + " on port " + port); Socket client = new Socket(serverName, port); System.out.println("Just connected to " + client.getRemoteSocketAddress()); // 创建FilePojo对象 FilePojo fpo = new FilePojo(); // 设定文件名 fpo.setFileName(fileName); // 设定文件大小 File f = new File(filePath + fileName); long fileLength = f.length(); fpo.setFileLength(fileLength); // 读取文件内容,并将其转换为byte[] FileInputStream fis = new FileInputStream(filePath + fileName); byte[] fileContent = new byte[(int) fileLength]; fis.read(fileContent, 0, (int) fileLength); fpo.setFileContent(fileContent); // 将FilePojo对象fpo写到Socket client指定的输出流 long start = System.currentTimeMillis(); ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream()); oos.writeObject(fpo); long end = System.currentTimeMillis(); System.out.println("It takes " + (end - start) + "ms"); oos.flush(); oos.close(); } catch(IOException e) { e.printStackTrace(); } } }
1 //服务器端代码 2 package com.pnf.transfer; 3 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectInputStream; 7 import java.net.ServerSocket; 8 import java.net.Socket; 9 10 public class ObjectServer 11 { 12 public static void main(String[] args) throws ClassNotFoundException 13 { 14 ServerSocket serverSocket; 15 FileOutputStream fos; 16 17 try 18 { 19 serverSocket = new ServerSocket(6666); 20 while(true) 21 { 22 Socket clientSocket = serverSocket.accept(); 23 // 从clientSocket获取ObjectInputStream对象 24 ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream()); 25 // 读取从客户端传递过来的FilePojo对象 26 FilePojo fpo = (FilePojo) ois.readObject(); 27 System.out.println(fpo.getFileName()); 28 System.out.println(fpo.getFileLength()); 29 30 // 初始化FileOutputStream对象fos 31 fos = new FileOutputStream("D:\\" + fpo.getFileName()); 32 // 将fpo中的内容写入fpo 33 fos.write(fpo.getFileContent(), 0, (int)fpo.getFileLength()); 34 35 fos.close(); 36 ois.close(); 37 } 38 } 39 catch(IOException ioe) 40 { 41 ioe.printStackTrace(); 42 } 43 } 44 }
先运行服务端,在运行客户端。
实验二:
1、服务端的代码和实验一中的一样。
2、手机客户端代码如下:
1 //android 手机客户端代码 2 package com.pnf.transfer; 3 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.ObjectOutputStream; 7 import java.net.Socket; 8 import android.app.Activity; 9 import android.os.Bundle; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 import android.widget.Toast; 14 15 public class AndroidObjectClientActivity extends Activity 16 implements 17 OnClickListener 18 { 19 private Button btn_send; 20 private String filePath = "/sdcard/image/"; 21 private String fileName = "bln.jpg"; 22 23 @Override 24 public void onCreate(Bundle savedInstanceState) 25 { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.main); 28 29 btn_send = (Button)this.findViewById(R.id.btn_send); 30 btn_send.setOnClickListener(this); 31 } 32 33 public void onClick(View v) 34 { 35 try 36 { 37 // 建立和服务器的连接 38 Socket socket = new Socket("192.168.0.10", 6666); 39 40 // 创建FilePojo对象 41 FilePojo fpo = new FilePojo(); 42 43 // 设定文件名 44 fpo.setFileName(fileName); 45 46 // 设定文件大小 47 File f = new File(filePath + fileName); 48 long fileLength = f.length(); 49 fpo.setFileLength(fileLength); 50 51 // 读取文件内容,并将其转换为byte[] 52 FileInputStream fis = new FileInputStream(filePath + fileName); 53 byte[] fileContent = new byte[(int) fileLength]; 54 fis.read(fileContent, 0, (int) fileLength); 55 fpo.setFileContent(fileContent); 56 57 // 将FilePojo对象fpo写到Socket client指定的输出流 58 long start = System.currentTimeMillis(); 59 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 60 oos.writeObject(fpo); 61 long end = System.currentTimeMillis(); 62 Toast.makeText(this.getApplicationContext(), "It takes " + (end - start) + "ms", Toast.LENGTH_LONG).show(); 63 oos.flush(); 64 oos.close(); 65 } 66 catch(Exception ioe) 67 { 68 ioe.printStackTrace(); 69 } 70 } 71 }
3、在AndroidManifest.xml中增加permission如下(粗体字部分):添加权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
4、先运行服务端,再在手机上运行客户端。
—————————————————————————————————————
JAVA操作Excel
1 package com.sinogain.test; 2 3 import java.io.File; 4 5 import jxl.Cell; 6 import jxl.Sheet; 7 import jxl.Workbook; 8 import jxl.write.Label; 9 import jxl.write.WritableSheet; 10 import jxl.write.WritableWorkbook; 11 12 public class OperateExcel { 13 public static void CreateExcel() { 14 try { 15 // 打开文件 16 WritableWorkbook book = Workbook.createWorkbook(new File("D:/test.xls")); 17 // 生成名为“第一页”的工作表,参数0表示这是第一页 18 WritableSheet sheet = book.createSheet("第一页", 0); 19 // 在Label对象的构造子中指名单元格位置是第一列第一行(0,0) 20 // 以及单元格内容为test 21 Label label = new Label(0, 0, "test"); 22 23 // 将定义好的单元格添加到工作表中 24 sheet.addCell(label); 25 26 /* 27 * 生成一个保存数字的单元格 必须使用Number的完整包路径,否则有语法歧义 单元格位置是第二列,第一行,值为789.123 28 */ 29 jxl.write.Number number = new jxl.write.Number(1, 0, 555.12541); 30 sheet.addCell(number); 31 32 // 写入数据并关闭文件 33 book.write(); 34 book.close(); 35 } catch (Exception e) { 36 System.out.println(e); 37 } 38 } 39 40 //读取Excel的类 41 public static String ReadExcel(String filePath,int Row,int colum){ 42 try { 43 Workbook book = Workbook.getWorkbook(new File(filePath)); 44 // 获得第一个工作表对象 45 Sheet sheet = book.getSheet(0); 46 // 得到第一列第一行的单元格 47 Cell cell1 = sheet.getCell(Row-1, colum-1); 48 String result = cell1.getContents(); 49 System.out.println(result); 50 book.close(); 51 return result; 52 } catch (Exception e) { 53 System.out.println(e); 54 } 55 return null; 56 } 57 58 public static void UpdateExcel() { 59 try { 60 // Excel获得文件 61 Workbook wb = Workbook.getWorkbook(new File("test.xls")); 62 // 打开一个文件的副本,并且指定数据写回到原文件 63 WritableWorkbook book = Workbook.createWorkbook(new File("test.xls"), 64 wb); 65 // 添加一个工作表 66 WritableSheet sheet = book.createSheet("第二页", 1); 67 sheet.addCell(new Label(0, 0, "第二页的测试数据")); 68 book.write(); 69 book.close(); 70 } catch (Exception e) { 71 System.out.println(e); 72 } 73 } 74 75 public static void CountExcel() { 76 try { 77 Workbook book = Workbook.getWorkbook(new File("测试1.xls")); 78 // 获得第一个工作表对象 79 Sheet sheet = book.getSheet(0); 80 // 得到第一列第一行的单元格 81 int columnum = sheet.getColumns();// 得到列数 82 int rownum = sheet.getRows();// 得到行数 83 System.out.println(columnum); 84 System.out.println(rownum); 85 for (int i = 0; i < rownum; i++)// 循环进行读写 86 { 87 for (int j = 0; j < columnum; j++) { 88 Cell cell1 = sheet.getCell(j, i); 89 String result = cell1.getContents(); 90 System.out.print(result); 91 System.out.print("\t"); 92 } 93 System.out.println(); 94 } 95 book.close(); 96 } catch (Exception e) { 97 System.out.println(e); 98 } 99 } 100 }
————————————————————————————————————————

浙公网安备 33010602011771号