目前用于Android的测试框架已经有很多,比如:Monkey、Appium和Robotium等。Monkey这是大家比较熟悉的,Android自带的系统工具。Monkey通过向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试,是用于测试软件的稳定性、健壮性的快速有效的方法。.Appium测试相当于黑盒测试。这个测试框架,一般用于测试UI逻辑的正确性,不能升入测试业务逻辑流程。
Robotium是一款面向Android端的开源自动化测试框架,Robotium结合Android自身提供的测试框架可以对应用程序进行自动化测试。另外,Robotium还支持对WebView的操作。Robotium的核心类是Solo,通过Solo可以对控件进行各种操作。
开发工具使用AndroidStudio,AndroidStudio的Junit版本是4.12,可以支持参数化测试。
在androidTest包下测试代码,AndroidStudio重新划分了工程的结构,分为androidTest、main、test三个文件夹,其中androidTest为Instrmentation测试包,main为源码包,test为单元测试包。Robotium是基于Instrmentation的测试框架,所以我们在这个包内写测试代码。
修改build.gradle
defaultConfig {
applicationId cfg.package
minSdkVersion cfg.minSdk
targetSdkVersion cfg.targetSdk
versionCode cfg.version_code
versionName cfg.version_name
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile 'com.android.support.test:rules:0.4.1'
compile 'com.jayway.android.robotium:robotium-solo:5.6.0'
}
注:注意这里testInstrumentationRunner使用的是android.support.test.runner.AndroidJUnitRunner
而不是android.test.InstrumentationTestRunner
.
@RunWith
实现通过使用@RunWith(AndroidJUnit4.class)
注解不需要再继承ActivityInstrumentationTestCase2
类,如果需要参数化执行可修改为@RunWith(Parameterized.class)
。很明显这种方式是基于Junit4实现的。
setUp()做一些启动测试前的准备工作,如创建Solo实例,启动Activity等。
@Before
public void setUp() throws Exception {
solo = new Solo(InstrumentationRegistry.getInstrumentation(),
activityTestRule.getActivity());
}
tearDown()中测试做一些善后的工作,如结束Activity等
@After
public void tearDown() throws Exception {
solo.finishOpenedActivities();
}
上述工作准备好之后,剩下的就是我们的测试主体方法了。为了方便主体方法一般以test开头。
@Test
public void testLogin() throws Exception {
AutoCompleteTextView name = (AutoCompleteTextView) solo.getView("atv_scarid");
EditText pwd = (EditText) solo.getView("et_pwd");
solo.enterText(name, "cnsldg");
solo.enterText(pwd, "666888");
solo.clickOnView(solo.getView("bt_login"));
}
整体结构如下:
@RunWith(AndroidJUnit4.class)
public class LoginAcitityTest {
@Rule
public ActivityTestRule activityTestRule =
new ActivityTestRule<>(LoginActivity.class);
private Solo solo;
@Before
public void setUp() throws Exception {
solo = new Solo(InstrumentationRegistry.getInstrumentation(),
activityTestRule.getActivity());
}
@After
public void tearDown() throws Exception {
solo.finishOpenedActivities();
}
@Test
public void testLogin() throws Exception {
AutoCompleteTextView name = (AutoCompleteTextView) solo.getView("atv_scarid");
EditText pwd = (EditText) solo.getView("et_pwd");
solo.enterText(name, ""); //清空
solo.enterText(pwd, ""); //清空
solo.enterText(name, "cnsldg");
solo.enterText(pwd, "666888");
solo.clickOnView(solo.getView("bt_login"));
boolean result = solo.waitForActivity(HistoryActivity.class, 1000);
Assert.assertEquals(true, result);
}
}
在测试方法上右键点击运行
@Deprecated
不建议使用。public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private Solo solo;
public MainActivityTest() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
}
@Override
protected void tearDown() throws Exception {
solo.finishOpenedActivities();
super.tearDown();
}
public void testAdd()
{
EditText et_num1 = (EditText) solo.getView("et_num1");
EditText et_num2 = (EditText) solo.getView("et_num2");
solo.enterText(et_num1, "1");
solo.enterText(et_num2, "2");
Button btn = (Button) solo.getView("btn");
TextView tv_sum = (TextView) solo.getView("tv_sum");
solo.clickOnView(btn);
//等待点击事件生效
solo.sleep(200);
assertEquals("3", tv_sum.getText().toString());
}
}
注意:测试代码并不是在主线程运行的,所以我们不能直接通过获取View来更改UI。
getView(int id)
getView(int id, int index) //index表示控件是界面上第几个相同控件
getView(String id) //xml中定义的id属性值
TextView textView = (TextView)solo.getView("hello");
assertEquals("hello world", textView.getText().toString());
//先获取控件,
enterText(EditText editText, String text)
//index表示页面上的第几个输入框
enterText(int index, String text)
waitForDialogToClose()
waitForDialogToClose(long timeout)
waitForDialogToOpen()
waitForDialogToOpen(long timeout)
long timeout:设置超时时间,单位为毫秒
//等待指定Activity的出现
waitForActivity(String)
waitForActivity(String, int)
waitForActivity(Class extends Activity)
waitForActivity(Class extends Activity, int)
String:Activity名称
int:超时时间,默认为20000,单位为毫秒
Class
//休眠指定时间
sleep(int time)
//从当前界面搜索指定的文本
searchText(String text)
//等待指定Log的出现
waitForLogMessage(String logMessage)
//等待控件的出现
waitForView(int id)
//等待文本的出现
waitForText(String text)
//等待某种加载条件的达成
boolean waitForCondition (Condition condition, int timeout)
//滑动到顶部
scrollTop()
//向上向下滚动屏幕
scrollUp()/scorllDown()
//滚动至ListView第line行
scrollListToLine(AbsListView absListView, int line)
//从其实x,y左边滑动至终点x,y坐标
drag(float fromX, float toX, float fromY, from toY)
//截图,name为图片的参数,默认路径是/sdcard/Robotium-Screenshots/
takeScreenshot()
takeScreenshot(String name)
//截取某段时间内一个序列
takeScreenshotSequence(String name)
//关闭当前已打开的所有Activity
finishOpenedActivities()
//点击返回键
goBack()
//不断点击返回键直至返回到指定Activity
goBackToActivity(String name)
// 收起键盘
hideSoftKeyboard()
//设置Activity转屏方向
setActivityOrientation(int orientation)