[转载]robotium脚本封装为APK,实现脱离手机数据线,使用按钮点击控制用例
最近一直在完成一些robotium的小功能,用来更方便的完成一些小功能的测试,或者可以说用来娱乐吧,幸得群内大神思路指点,就此引申,终于把这个功能得以实现
---------------将robotium脚本封装为APK,使用按钮控制用例运行覆盖程度,测试结果以简单的xml文件输入到手机SD卡目录下----------------------
废话不多说,转正题:
一、首先明确一点,这篇文章,是在你所编写的robotium脚本运行无异常的前提下实施
二、阐明思路:
1.我们需要一个运行良好的,逻辑正常的robotium工程
2.我们需要一个可以将脚本用例运行结果保存至本地的方法
3.我们需要一个activity,一个按钮,以及一个按钮点击事件去运行我们的robotium脚本
三、先介绍脚本用例运行结果的功能,我们都知道robotium用例的运行是依赖junit的Instrumentation的,所以,关于结果的输出,需要我们重写InstrumentationTestRunner类
package baih;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
public class InstrumentationTestRunner extends
android.test.InstrumentationTestRunner {
private
Writer mWriter;
private
XmlSerializer mTestSuiteSerializer;
private long
mTestStarted;
private
static final String JUNIT_XML_FILE = "TEST-all.xml";
@Override
public void
onStart() {
try{
File fileRobo = new
File(getTestResultDir(getTargetContext()));
if(!fileRobo.exists()){
fileRobo.mkdir();
}
if(isSDCardAvaliable()){
File resultFile = new
File(getTestResultDir(getTargetContext()),JUNIT_XML_FILE);
startJUnitOutput(new FileWriter(resultFile));
}else{
startJUnitOutput(new FileWriter(new
File(getTargetContext().getFilesDir(), JUNIT_XML_FILE)));
}
}
catch(IOException e){
throw new RuntimeException(e);
}
super.onStart();
}
void
startJUnitOutput(Writer writer) {
try {
mWriter = writer;
mTestSuiteSerializer = newSerializer(mWriter);
mTestSuiteSerializer.startDocument(null, null);
mTestSuiteSerializer.startTag(null, "testsuites");
mTestSuiteSerializer.startTag(null, "testsuite");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private
boolean isSDCardAvaliable(){
return Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED);
}
private
String getTestResultDir(Context context){
String packageName = "/" + "robotium";
String filepath = context.getCacheDir().getPath() +
packageName;
if(android.os.Build.VERSION.SDK_INT < 8){
if(isSDCardAvaliable()){
filepath =
Environment.getExternalStorageDirectory().getAbsolutePath()+
packageName;
}
}else{
if(isSDCardAvaliable()){
filepath =
Environment.getExternalStorageDirectory().getAbsolutePath()+
packageName;
}
}
return filepath;
}
private
XmlSerializer newSerializer(Writer writer) {
try {
XmlPullParserFactory pf = XmlPullParserFactory.newInstance();
XmlSerializer serializer = pf.newSerializer();
serializer.setOutput(writer);
return serializer;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void
sendStatus(int resultCode, Bundle results) {
super.sendStatus(resultCode, results);
switch (resultCode) {
case REPORT_VALUE_RESULT_ERROR:
case REPORT_VALUE_RESULT_FAILURE:
case REPORT_VALUE_RESULT_OK:
try {
recordTestResult(resultCode, results);
} catch (IOException e) {
throw new RuntimeException(e);
}
break;
case REPORT_VALUE_RESULT_START:
recordTestStart(results);
default:
break;
}
}
void
recordTestStart(Bundle results) {
mTestStarted = System.currentTimeMillis();
}
void
recordTestResult(int resultCode, Bundle results) throws IOException
{
float time = (System.currentTimeMillis() - mTestStarted) /
1000.0f;
String className = results.getString(REPORT_KEY_NAME_CLASS);
String testMethod = results.getString(REPORT_KEY_NAME_TEST);
String stack = results.getString(REPORT_KEY_STACK);
int current = results.getInt(REPORT_KEY_NUM_CURRENT);
int total = results.getInt(REPORT_KEY_NUM_TOTAL);
mTestSuiteSerializer.startTag(null, "testcase");
mTestSuiteSerializer.attribute(null, "classname", className);
mTestSuiteSerializer.attribute(null, "name", testMethod);
if (resultCode != REPORT_VALUE_RESULT_OK) {
mTestSuiteSerializer.startTag(null, "failure");
if (stack != null) {
String reason = stack.substring(0, stack.indexOf('n'));
String message = "";
int index = reason.indexOf(':');
if (index > -1) {
message = reason.substring(index+1);
reason = reason.substring(0, index);
}
mTestSuiteSerializer.attribute(null, "message", message);
mTestSuiteSerializer.attribute(null, "type", reason);
mTestSuiteSerializer.text(stack);
}
mTestSuiteSerializer.endTag(null, "failure");
} else {
mTestSuiteSerializer.attribute(null, "time", String.format("%.3f",
time));
}
mTestSuiteSerializer.endTag(null,
"testcase");
if (current == total) {
mTestSuiteSerializer.startTag(null, "system-out");
mTestSuiteSerializer.endTag(null, "system-out");
mTestSuiteSerializer.startTag(null, "system-err");
mTestSuiteSerializer.endTag(null, "system-err");
mTestSuiteSerializer.endTag(null, "testsuite");
mTestSuiteSerializer.flush();
}
}
@Override
public void
finish(int resultCode, Bundle results) {
endTestSuites();
super.finish(resultCode, results);
}
void
endTestSuites() {
try {
mTestSuiteSerializer.endTag(null, "testsuites");
mTestSuiteSerializer.endDocument();
mTestSuiteSerializer.flush();
mWriter.flush();
mWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
重 写InstrumentationTestRunner类后,需要在我们的测试工程下设置run Configurations,在你的测试工程上右键-移至Run As-run Configurations-android JUnit Test,更改你工程运行的Instrumentation runner,如果不更改,默认是android.test.InstrumentationTestRunner
红色框体圈住的位置需要更改为你重写的InstrumentationTestrunner路径
完成以上步骤后,你再运行robotium脚本,就会在你的手机根目录下名为robotium的文件夹中生成一个名为TEST-ALL.xml的文件,用来记录你的脚本运行结果,结构如下:
运行正常的结果,会显示运行时长,运行错误的用例,会打印错误信息,至于错误信息中的中文提示,是我用例中自己加的断言,各位可以自行斟酌
完成这一步,就可以实施我们的封装APK计划了。封装APK,第一,我们需要一个activity
界面比较挫,将就看。关于activity的创建和声明,请各位自行百度
activity中可以放置一个或多个按钮,由于robotium可以使用命令行进行启动运行,所以我们可以使用不同按钮的onclick事件来执行不同的脚本
如上:附上各种命令行启动命令:
运行所有测试用例:
adb shell am instrument -w
packagename/InstrumentationTestRunnername
运行单个测试类或某个TestSuite
adb shell am instrument -e class packagename.testclassname -w
packagename/InstrumentationTestRunnername
运行某个测试类里面的某个测试方法
adb shell am instrument -e class package名.测试类名#方法名 -w
工程名.package名/InstrumentationTestRunner class名
注意:请在编写代码前,在CMD中测试你所使用的命令是否可以正常运行
完成以上步骤后,我们可以将整个测试工程打包为APK文件,记得签名需要签debug签名,然后......安装到你的手机上,拔掉该死的数据线,点击按钮,开始自动虐你的手机吧:)
号外!号外!
由于手头上一直没有android level 17及以上版本的手机,有一个shell命令启动脚本的BUG,发生在SDK level 17及以上
API>=17中加入了INTERACT_ACROSS_USERS_FULL,目的在于允许不同用户的应用之间可以产生交互,了安全,因此在交互时会校验userSerialNumber,,发现用户标识不匹配,导致权限校验失败,就会产生startInstrumentation asks to run as user -2 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL的报错,导致脚本无法调用
群里尝试,发现在17及以上版本,命令中需要加入--user 0参数
adb shell am instrument --user 0 -w packagename/InstrumentationTestRunnername
可以在调用时使用Build.VERSION.SDK_INT<17来对当前版本做判断选择合适的命令行启动方式