在本地执行的单元测试,不需要运行在物理设备或模拟器上,可以测试一些与Android框架无关的代码。
在*build.gradle (Module:app)*添加JUnit4依赖
在*包名(test)*下创建单元测试类,或者直接在需要创建的类的类名上右键->Go To->Test, Create a new test…
选择需要进行测试的方法,在setUp中创建类。
public class EmailValidator {
public static boolean isValidEmail(String email) {
if (null == email || "".equals(email))
return false;
Pattern p = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
Matcher m = p.matcher(email);
return m.matches();
}
}
单元测试:
public class EmailValidatorTest {
@Before
public void setUp() throws Exception {
}
@Test
public void isValidEmail() throws Exception {
assertEquals(EmailValidator.isValidEmail("name@email.com"), true);
}
}
运行在设备上的测试,但测试与设备相关或者与Android控件相关的功能时,需要选择Instrumented Unit Tests。
测试程序放在*module-name/src/androidTest/java/*。
需要在build.gradle下添加如下依赖。
dependencies {
androidTestCompile 'com.android.support:support-annotations:24.0.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
// Optional -- Hamcrest library
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
// Optional -- UI testing with Espresso
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Optional -- UI testing with UI Automator
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}
解决同时引入 support-annotations
和espresso-core
的冲突。
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
配置AndroidJUnitRunner为默认的instrumentation 运行方式。
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
在测试类前面添加注解 @RunWith(AndroidJUnit4.class)
下面是一个对于UI控件的Instrumented 测试。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:text="@string/hello_world" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:hint="Enter your name here"
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Say hello!"
android:layout_below="@+id/editText"
android:onClick="sayHello"/>
</RelativeLayout>
MainActivity.java
public void sayHello(View v){
TextView textView = (TextView) findViewById(R.id.textView);
EditText editText = (EditText) findViewById(R.id.editText);
textView.setText("Hello, " + editText.getText().toString() + "!");
}
测试程序
package com.example.testing.testingexample;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.action.ViewActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityInstrumentationTest {
private static final String STRING_TO_BE_TYPED = "Peter";
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
@Test
public void sayHello(){
onView(withId(R.id.editText)).perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); //找到id为editText的输入框,键入"Peter",关闭软键盘(实际操作时需要考虑输入法语言)
onView(withText("Say hello!")).perform(click()); //点击文本为"Say hello!"的按钮
String expectedText = "Hello, " + STRING_TO_BE_TYPED + "!";
onView(withId(R.id.textView)).check(matches(withText(expectedText))); //判断id为textView的文本框内容是否与期望符合
}
}
运行测试可以在虚拟机或物理设备上看到上述的操作,模拟键盘输入时,需要注意因为输入法语言可能带来的不一致。
Show Passed按钮
显示所有方法的测试。
Automating User Interface Tests用于测试UI界面,为用户提供高质量的用户界面和稳定的交互。
两种Automating UI Tests:
Testing UI for a Single App :确保交互界面正确。使用espresso框架。
Testing UI for Multiple Apps :测试多终端行为的正确,如不同终端的app之间通信。使用uiautomator框架。
基于Instrumentation的开源自动化测试框架,规模小、简洁,API精确,编写测试代码简单,容易快速上手。但不能跨app。
为了避免动画可能带来的错误,需要在开发者选项中关闭下面几项动画。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {
public static String STRING_TO_BE_TYPED;
// @Rule注解来启动一个activity
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
// @Before注解表示在测试前执行
@Before
public void initValidString() {
// Specify a valid string.
STRING_TO_BE_TYPED = "espresso";
}
@Test
public void changeText_sameActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));
}
@Test
public void changeText_newActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput)).perform(typeText(STRING_TO_BE_TYPED),
closeSoftKeyboard());
onView(withId(R.id.activityChangeTextBtn)).perform(click());
// This view is in a different Activity, no need to tell Espresso.
onView(withId(R.id.show_text_view)).check(matches(withText(STRING_TO_BE_TYPED)));
}
}
完整代码:https://github.com/googlesamples/android-testing/tree/master/ui/espresso/BasicSample
onView() 返回一个 ViewInteraction对象用于测试控件,没找到控件会扔出一个NoMatchingViewException。
onView(withText("Sign-in"));
onView(withId(R.id.button_signin));
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
onData() 返回一个DataInteraction对象,没找到控件也会扔出一个NoMatchingViewException。
onData(allOf(is(instanceOf(Map.class)),
hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));
ViewInteraction.perform() 和 DataInteraction.perform()的参数为一个或多个 ViewAction对象。
可以指定的对象:ViewActions.click()、ViewActions.typeText()、ViewActions.scrollTo()、ViewActions.pressKey()、ViewActions.clearText()。
用于截断activity或service发出的intent,验证其传递的内容是否正确。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
}
@Large
@RunWith(AndroidJUnit4.class)
public class SimpleIntentTest {
private static final String MESSAGE = "This is a test";
private static final String PACKAGE_NAME = "com.example.myfirstapp";
/* Instantiate an IntentsTestRule object. */
@Rule
public IntentsTestRule≶MainActivity> mIntentsRule =
new IntentsTestRule≶>(MainActivity.class);
@Test
public void verifyMessageSentToMessageActivity() {
// Types a message into a EditText element.
onView(withId(R.id.edit_message))
.perform(typeText(MESSAGE), closeSoftKeyboard());
// Clicks a button to send the message to another
// activity through an explicit intent.
onView(withId(R.id.send_message)).perform(click());
// Verifies that the DisplayMessageActivity received an intent
// with the correct package name and message.
intended(allOf(
hasComponent(hasShortClassName(".DisplayMessageActivity")),
toPackage(PACKAGE_NAME),
hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));
}
}
完整代码:https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IntentsBasicSample
用于检测activity里面webview的行为。
dependencies {
// Other dependencies ...
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
}
需测试的WebView必须在指定activity时设置enable JavaScript的,我们可以选择WebView中的HTML元素并模拟用户操作。
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WebViewActivityTest {
private static final String MACCHIATO = "Macchiato";
private static final String DOPPIO = "Doppio";
@Rule
public ActivityTestRule mActivityRule =
new ActivityTestRule(WebViewActivity.class,
false /* Initial touch mode */, false /* launch activity */) {
@Override
protected void afterActivityLaunched() {
// Enable JavaScript.
onWebView().forceJavascriptEnabled();
}
}
@Test
public void typeTextInInput_clickButton_SubmitsForm() {
// Lazily launch the Activity with a custom start Intent per test
mActivityRule.launchActivity(withWebFormIntent());
// Selects the WebView in your layout.
// If you have multiple WebViews you can also use a
// matcher to select a given WebView, onWebView(withId(R.id.web_view)).
onWebView()
// Find the input element by ID
.withElement(findElement(Locator.ID, "text_input"))
// Clear previous input
.perform(clearElement())
// Enter text into the input element
.perform(DriverAtoms.webKeys(MACCHIATO))
// Find the submit button
.withElement(findElement(Locator.ID, "submitBtn"))
// Simulate a click via JavaScript
.perform(webClick())
// Find the response element by ID
.withElement(findElement(Locator.ID, "response"))
// Verify that the response page contains the entered text
.check(webMatches(getText(), containsString(MACCHIATO)));
}
}
完整代码:https://github.com/googlesamples/android-testing/tree/master/ui/espresso/WebBasicSample
ViewInteraction.check() 或 DataInteraction.check() 验证控件是否满足期望的状态。这两个方法必须传一个ViewAssertion对象作为参数,断言失败会扔出一个AssertionFailedError。
可用的断言
doesNotExist:当前等级没有符合标准的view
matches:特定view符合给定标准
selectedDescendentsMatch:父view的特定子view存在且满足给定标准
https://io2015codelabs.appspot.com/codelabs/android-studio-testing#1
https://developer.android.com/training/testing/index.html