Android test framework
转载请注明来自:http://blog.csdn.net/liaoqianchuan00/article/details/23032357
1. 基本
1. 常用Assertions
l assertEquals
l assertFalse?
l assertNotNull
l assertNotSame
l assertNull
l assertSame
l assertTrue
l fail
2. 自定义输出语句
public void testMax() {
final int a = 1;
final int b = 2;
final int expected = b;
final int actual = Math.max(a, b);
assertEquals("Expection " + expected + " but was " + actual, expected, actual);
}
|
3. 控件Assertions
l assertBaselineAligned: Asserts that two views arealigned on their baseline, that is their baselines are on the same y location.
l assertBottomAligned: Asserts that two views arebottom aligned, that is their bottom edges are on the same y location.
l assertGroupContains: Asserts that the specifiedgroup contains a specific child once and only once.
l assertGroupIntegrity: Asserts the specified group'sintegrity. The children count should be >= 0 and each child should benon-null.
l assertGroupNotContains: Asserts that the specifiedgroup does not contain a specific child.
l assertHasScreenCoordinates: Asserts that a view hasa particular x and y position on the visible screen.
l assertHorizontalCenterAligned: Asserts that the testview is horizontally center aligned with respect to the reference view.
l assertLeftAligned: Asserts that two views are leftaligned, that is their left edges are on the same x location. An optionalmargin can also be provided.
l assertOffScreenAbove: Asserts that the specifiedview is above the visible screen.
l assertOffScreenBelow: Asserts that the specifiedview is below the visible screen.
l assertOnScreen: Asserts that a view is on thescreen.
l assertRightAligned: Asserts that two views areright-aligned, that is their right edges are on the same x location. Anoptional margin can also be specified.
l assertTopAligned: Asserts that two views aretop-aligned, that is their top edges are on the same y location. An optionalmargin can also be specified.
l assertVerticalCenterAligned: Asserts that the testview is vertically center aligned with respect to the reference view.
4. TouchUtils
l Clicking on a View and releasing it
l Tapping on a View, that is touching it and quicklyreleasing
l Long clicking on a View
l Dragging the screen
public void testListScrolling() {
mListView.scrollTo(0, 0);
TouchUtils.dragQuarterScreenUp(this, mActivity);
TouchUtils.dragQuarterScreenUp(this, mActivity);
TouchUtils.dragQuarterScreenUp(this, mActivity);
TouchUtils.dragQuarterScreenUp(this, mActivity);
TouchUtils.tapView(this, mListView);
final int expectedItemPosition = 6;
final int actualItemPosition =
mListView.getFirstVisiblePosition();
assertEquals("Wrong position",
expectedItemPosition, actualItemPosition);
final String expected = "Anguilla";
final String actual = mListView.getAdapter().
getItem(expectedItemPosition).toString();
assertEquals("Wrong content", actual, expected);
}
|
5. Mock对象
l MockApplication: A mock implementation of the Application class. All methods arenon-functional and throw UnsupportedOperationException.
l MockContentProvider: A mock implementation of ContentProvider. All methods arenon-functional and throw UnsupportedOperationException.
l MockContentResolver: A mock implementation of the ContentResolver class that isolatesthe test code from the real content system. All methods are non-functional andthrow UnsupportedOperationException.
l MockContext: A mock Context class. This can be used to inject otherdependencies. All methods are non-functional and throwUnsupportedOperationException.
l MockCursor: A mock Cursor class that isolates the test code from real Cursorimplementation. All methods are non-functional and throwUnsupportedOperationException.
l MockDialogInterface: A mock implementation of DialogInterface class. All methods arenon-functional and throw UnsupportedOperationException.
l MockPackageManager: A mock implementation of PackageManager class. All methods arenon-functional and throw UnsupportedOperationException.
l MockResources: A mock Resources class. All methods are non-functional and throw UnsupportedOperationException.
2. 框架结构
1 AndroidTestCase
当你需要用到Activity Context的时候就使用这个类,比如Resources,database,file system,你可以是用mContext来是用context。你可以用ontext.startActivity()来启动多个Activity。
有很多个testcase继承自这个类:
l ApplicationTestCase<T extends Application>
l ProviderTestCase2<T extends ContentProvider>
l ServiceTestCase<T extends Service>
2 Instrumentation
这个是用来监视Activity和Application的,你可以用它来控制Activity的生命周期和用户交互事件。
l 我们可以是用Instrumentation.ActivityMonitor来监测一个Activity。例如:
public void testFollowLink() {
final Instrumentation inst = getInstrumentation();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
intentFilter.addDataScheme("http");
intentFilter.addCategory(Intent.CATEGORY_BROWSABLE);
ActivityMonitor monitor = inst.addMonitor(
IntentFilter, null, false);
assertEquals(0, monitor.getHits());
TouchUtils.clickView(this, mLink); monitor.waitForActivityWithTimeout(5000);
assertEquals(1, monitor.getHits()); inst.removeMonitor(monitor);
}
|
l 使用Instrumentation来测试activities,它的生命周期的函数不会自动调用,只有onCreate方法会自动调用,你可以调用其他的生命周期通过getInstrumentation().callActivityOnXXX。
3 InstrumentationTestCase
它的子类:
l ActivityTestCase
l ProviderTestCase2<T extends ContentProvider>
l SingleLaunchActivityTestCase<T extends Activity>
l SyncBaseInstrumentation
l ActivityInstrumentationTestCase2<T extends Activity>
l ActivityUnitTestCase<T extends Activity>
注意:android.test.ActivityInstrumentationTestCase从Android1.5开始已经不建议使用了,可以是用android.test.ActivityInstrumentationTestCase2替代。
3. 例子程序
1. 被测试程序
package com.vogella.android.test.simpleactivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("URL", "http://www.");
startActivity(intent);
}
}
|
2. 单元测试
package com.vogella.android.test.simpleactivity.test;
import android.content.Intent;
import android.test.TouchUtils;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.Button;
import com.vogella.android.test.simpleactivity.MainActivity;
public class MainActivityUnitTest extends
android.test.ActivityUnitTestCase<MainActivity> {
private int buttonId;
private MainActivity activity;
public MainActivityUnitTest() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
Intent intent = new Intent(getInstrumentation().getTargetContext(),
MainActivity.class);
startActivity(intent, null, null);
activity = getActivity();
}
public void testLayout() {
buttonId = com.vogella.android.test.simpleactivity.R.id.button1;
assertNotNull(activity.findViewById(buttonId));
Button view = (Button) activity.findViewById(buttonId);
assertEquals("Incorrect label of the button", "Start", view.getText());
}
public void testIntentTriggerViaOnClick() {
buttonId = com.vogella.android.test.simpleactivity.R.id.button1;
Button view = (Button) activity.findViewById(buttonId);
assertNotNull("Button not allowed to be null", view);
view.performClick();
// TouchUtils cannot be used, only allowed in
// InstrumentationTestCase or ActivityInstrumentationTestCase2
// Check the intent which was started
Intent triggeredIntent = getStartedActivityIntent();
assertNotNull("Intent was null", triggeredIntent);
String data = triggeredIntent.getExtras().getString("URL");
assertEquals("Incorrect data passed via the intent",
"http://www.", data);
}
}
|
3. 功能测试
package com.vogella.android.test.simpleactivity.test;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.test.ViewAsserts;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.vogella.android.test.simpleactivity.R;
import com.vogella.android.test.simpleactivity.MainActivity;
import com.vogella.android.test.simpleactivity.SecondActivity;
public class MainActivityFunctionalTest extends
ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity activity;
public MainActivityFunctionalTest() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
activity = getActivity();
}
public void testStartSecondActivity() throws Exception {
// add monitor to check for the second activity
ActivityMonitor monitor =
getInstrumentation().
addMonitor(SecondActivity.class.getName(), null, false);
// find button and click it
Button view = (Button) activity.findViewById(R.id.button1);
// TouchUtils handles the sync with the main thread internally
TouchUtils.clickView(this, view);
// to click on a click, e.g., in a listview
// listView.getChildAt(0);
// wait 2 seconds for the start of the activity
SecondActivity startedActivity = (SecondActivity) monitor
.waitForActivityWithTimeout(2000);
assertNotNull(startedActivity);
// search for the textView
TextView textView = (TextView) startedActivity.findViewById(R.id.resultText);
// check that the TextView is on the screen
ViewAsserts.assertOnScreen(startedActivity.getWindow().getDecorView(),
textView);
// validate the text on the TextView
assertEquals("Text incorrect", "Started", textView.getText().toString());
// press back and click again
this.sendKeys(KeyEvent.KEYCODE_BACK);
TouchUtils.clickView(this, view);
}
}
|
Robotium
从https://code.google.com/p/robotium/wiki/RobotiumTutorials下载例子程序
public void testDisplayWhiteBox() {
//Defining our own values to multiply
float vFirstNumber = 10;
float vSecondNumber = 20;
float vResutl = vFirstNumber * vSecondNumber ;
//Access First value (edit-filed) and putting firstNumber value in it
EditText vFirstEditText = (EditText) solo.getView(R.id.EditText01);
solo.clearEditText(vFirstEditText);
solo.enterText(vFirstEditText, String.valueOf(vFirstNumber));
//Access Second value (edit-filed) and putting SecondNumber value in it
EditText vSecondEditText = (EditText) solo.getView(R.id.EditText02);
solo.clearEditText(vSecondEditText);
solo.enterText(vSecondEditText, String.valueOf(vSecondNumber));
//Click on Multiply button
solo.clickOnButton("Multiply");
assertTrue(solo.searchText(String.valueOf(vResutl)));
TextView outputField = (TextView) solo.getView(R.id.TextView01);
//Assert to verify result with visible value
assertEquals(String.valueOf(vResutl), outputField.getText().toString());
}
|
参考地址:
https://code.google.com/p/robotium/
https://code.google.com/p/robotium/wiki/RobotiumTutorials
1 简介
Robotium是一款国外的Android自动化测试框架,主要针对Android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击、长按、滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium结合Android官方提供的测试框架达到对应用程序进行自动化的测试。另外,Robotium 4.0版本已经支持对WebView的操作。Robotium 对Activity,Dialog,Toast,Menu 都是支持的。
类似于selenium。
2 API
Solo类中提供了自动点击、取得、拖拽、搜索等各种方法。 声明Solo类型的成员变量privateSolo solo;
Activity&Fragment
assertCurrentActivity(text,Activity.class)- Ensure that the current activity equals the second parameter.
getCurrentActivity() .getFragmentManager().findFragmentById()-S earches for a fragment.
waitForActivity(SecondActivity.class,2000)- Waits for the specified activity for 2 seconds
点击
clickOnButton(int)—Clickson a Button with a given index.
clickOnButton(String)—Clickson a Button with a given text. clickOnCheckBox(int)—Clicks on a CheckBox with agiven index. clickOnView(View)—Clicks ona given View.
clickOnText(String)—Clickson a View displaying a given text. clickLongOnText(String)—Long clicks on agiven View. clickOnRadioButton(int)—Clicks on a RadioButton with a given index.clickOnScreen(float, float)—Clicks on a given coordinate on the screen.
clickInList(x);-Click on item number x in a ListView
clickOnSearch-Allows to click on part of the screen.
pressSpinnerItem(0,2);-Presses an item in a Spinner
sendKey(Solo.MENU);-Sends the menu key event.
goBack()-Pressthe back button
取得
getCurrentActivity()—Returnsthe current Activity.
GetText(String)—Returnsa TextView which shows a given text.
getView(int)—Returnsa View with a given id.
getEditText(String)—Returnsan EditText which shows a given text. getImage(int)—Returns an ImageView with a given index.
拖拽
drag(float,float, float, float, int)—Simulate touching a given location and dragging it toa new location.
搜索
searchText(String)—Searchesfor a text string and returns true if at least one item is found with theexpected text.
searchEditText(String)—Searchesfor a text string in the EditText objects located in the current Activity. S
earchButton(String,boolean)—Searches for a Button with the given text string and returns true ifat least one Button is found.
waitForText(text)
输入
enterText()-Entersa text.
界面判断
isCheckBoxChecked()-Checksif the checkbox is checked.
截屏
takeScreenshot()-Savesa screenshot on the device inthe /sdcard/Robotium-Screenshots/ folder. Requiresthe android.permission.WRITE_EXTERNAL_STORAGE permission in theAndroidManifest.xml ofthe application under test.
4. QA
1. 在不知道ID的情况下怎么获取特定的view?
答:例如,ArrayList<TextView> aa = solo.getCurrentViews(TextView.class), 然后断点调试查看是哪个view,TextViewtextview = aa.get(5);
2. 如何滚动和拖动?
答:solo.scrollXXX();solo.drag(初始X坐标,目标X坐标,Y,toY,步数); X,Y坐标可以通过HierarchyViewer工具获得,也可以通过Year.getLocationOnScreen(zuobiao)。
3. Robotium和robolectric区别?
答:Robotium是通过ui线程进行测试,一般用于对应传统的集成或系统测试;
Robolectric 是单元测试框架,好处是第一运行在JVM上,速度、性能高;
解决了Android自身因为环境问题的缺点以及减少了许多用Mock的地方。
4. 如何识别webview对象?
答:可以根据XPATH来进行点击和输入
solo.clickOnWebElement(By.xpath(text));
solo.enterTextInWebElement(By.xpath(text),s);
或者根据ID来获取
solo.clickOnWebElement(By.id(text));
solo.enterTextInWebElement(By.id(text), s);
Mockito
1. 简介
流行的mock框架
# jMock
http:///
# EasyMock
http:///
# Mockito
http://code.google.com/p/mockito/
使用Mockito不能用在下面的情况:
final classes
anonymous classes
primitive types
2. 使用方法
when(....).thenReturn(....)
或者
doReturn(object).when(kdskfsk).methodCall
使用verify来保证方法被调用到了
例如:
@Test
public void test1() {
MyClass test = Mockito.mock(MyClass.class);
// define return value for method getUniqueId()
test.when(test.getUniqueId()).thenReturn(43);
// TODO use mock in test....
// now check if method testing was called with the parameter 12
Mockito.verify(test).testing(Matchers.eq(12));
// was the method called twice?
Mockito.verify(test, Mockito.times(2));
}
|
3. Mock和Spy的区别
如果你mock了一个类,那么这个类的所有的函数都被Mockito改写了(如果是没有返回值的函数,则什么都不作,如果是有返回值,会返回默认值,比如布尔型的话返回false,List的话会返回一个空的列表,int的话会返回0等等),如果你Spy了一个类,那么所有的函数都没有被改变,除了那些被你打过桩的函数。看例子:
public class TestServiceImpl
{
public int getOrderCounts()
{
return 10;
}
}
@Test
public void MockVsSpy()
{
TestServiceImpl service = mock(TestServiceImpl.class);
//输出0,因为该函数被Mockito改写了
System.out.println("Order counts of mock object" + service.getOrderCounts());
when(service.getOrderCounts()).thenReturn(2);
//输出2, 因为我们给这个函数打了桩
System.out.println("Order counts of mock object AFTER stubs " + service.getOrderCounts());
service = new TestServiceImpl();
service = spy(service);
//输出10, 因为Mockito spy 不会改写已有的函数
System.out.println("Order counts of spy object" + service.getOrderCounts());
when(service.getOrderCounts()).thenReturn(2);
//输出2, 因为我们给这个函数打了桩
System.out.println("Order counts of spy object AFTER stubs " + service.getOrderCounts());
}
|
4. 如何写自定义的参数匹配器
看例子
public class Account
{
private String name;
private String adddress;
public Account(String name, String address)
{
this.name = name;
this.adddress = address;
}
...get/set 函数
}
public interface AccountDao
{
public void addAccount(Account a);
}
public class AccountServiceImpl
{
AccountDao dao;
public AccountServiceImpl(AccountDao dao)
{
this.dao = dao;
}
public void addAccount(String name, String address)
{
dao.addAccount(new Account(name, address));
}
}
public class AccountServiceImplTest
{
@Test
public void addAccount()
{
AccountDao dao = mock(AccountDao.class);
AccountServiceImpl service = new AccountServiceImpl(dao);
service.addAccount("obama", "white house");
verify(dao).addAccount(new Account("obama", "white house"));
}
}
|
上面的例子会失败,因为Mockito在做参数匹配时是根据equals函数的结果来判断两个参数是不是一样的。而我们的Account类并没有对equals作特殊的实现,所以会失败。修正的方法有三个,一个是改写Account类的equals函数。一个是用Mockito的反射相等匹配,就是把最后一句改成。
verify(dao).addAccount(refEq(new Account("obama", "white house")));
|
最后一种方法是写一个自定义的参数匹配器,如果Account的代码不是你控制的,那么你就只能选这种方法了。这时候最后一句要改成这样:
verify(dao).addAccount(argThat(new ArgumentMatcher<Account>()
{
@Override
public boolean matches(Object argument)
{
Account person = (Account)argument;
return person.getName().equals("obama") && person.getAddress().equals("white house") ? true : false;
}
}));
|
在Android中使用Mockito:
需要下载下面3个库文件。
http://dexmaker./files/dexmaker-1.0.jar
http://dexmaker./files/dexmaker-mockito-1.0.jar
https://code.google.com/p/mockito/
|