关键词:Android Testing, JUnit, Mockito, Espresso, Robolectric
Android单元测试可以分为两类:
-
Local unit tests:
- 测试代码位于
module-name/src/test/java/
目录下 - 运行在本地JVM上
- 测试代码位于
-
Instrumented tests:
- 测试代码位于
module-name/src/androidTest/java/
目录下 - 必须运行在Android真机或模拟器上
- 测试代码位于
JUnit 4 注解
-
@Before
: Use this annotation to specify a block of code that contains test setup operations. The test class invokes this code block before each test. You can have multiple@Before
methods but the order in which the test class calls these methods is not guaranteed. -
@After
: This annotation specifies a block of code that contains test tear-down operations. The test class calls this code block after every test method. You can define multiple@After
operations in your test code. Use this annotation to release any resources from memory. -
@Test
: Use this annotation to mark a test method. A single test class can contain multiple test methods, each prefixed with this annotation. -
@Rule
: Rules allow you to flexibly add or redefine the behavior of each test method in a reusable way. In Android testing, use this annotation together with one of the test rule classes that the Android Testing Support Library provides, such as ActivityTestRule
or ServiceTestRule
. -
@BeforeClass
: Use this annotation to specify static methods for each test class to invoke only once. This testing step is useful for expensive operations such as connecting to a database. -
@AfterClass
: Use this annotation to specify static methods for the test class to invoke only after all tests in the class have run. This testing step is useful for releasing any resources allocated in the@BeforeClass
block. -
@Test(timeout=)
: Some annotations support the ability to pass in elements for which you can set values. For example, you can specify a timeout period for the test. If the test starts but does not complete within the given timeout period, it automatically fails. You must specify the timeout period in milliseconds, for example:@Test(timeout=5000)
.
1. Local Unit Tests
在build.gradle
中配置测试依赖:
dependencies {
......
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.hamcrest:hamcrest-all:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
}
一个简单的例子:
public class EmailValidatorTest {
@Test
public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
}
...
}
By default, the Android Plug-in for Gradle executes your local unit tests against a modified version of the
android.jar
library, which does not contain any actual code. Instead, method calls to Android classes from your unit test throw an exception.
使用 Mockito mock 测试代码中对Android的依赖:
@RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext;
@Test
public void readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
when(mMockContext.getString(R.string.hello_word))
.thenReturn(FAKE_STRING);
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// ...when the string is returned from the object under test...
String result = myObjectUnderTest.getHelloWorldString();
// ...then the result should be the expected one.
assertThat(result, is(FAKE_STRING));
}
}
Robolectric
Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds.
- 配置Robolectric
testCompile "org.robolectric:robolectric:3.1.4"
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class SandwichTest {
}
Note that you must specify the constants
field which points to the BuildConfig.class
generated by the build system. Robolectric uses the constants in the class to compute the output paths used by Gradle when building your project. Without these values, Robolectric will not be able to find your merged manifest, resources, or assets.
一个简单的例子:
@RunWith(RobolectricTestRunner.class)
public class WelcomeActivityTest {
@Test
public void clickingLogin_shouldStartLoginActivity() {
WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
activity.findViewById(R.id.login).performClick();
Intent expectedIntent = new Intent(activity, LoginActivity.class);
assertThat(shadowOf(activity).getNextStartedActivity()).isEqualTo(expectedIntent);
}
}
The primary way to customize Robolectric is done via the
@Config
annotation.
-
Configure SDK Level
Robolectric 默认会运行 manifest 中指定的targetSdkVersion
版本,如果某些代码需要在不同的SDK版本下测试,可以如下设置:
@Config(sdk = Build.VERSION_CODES.JELLY_BEAN)
public class SandwichTest {
@Config(sdk = Build.VERSION_CODES.KITKAT)
public void getSandwich_shouldReturnHamSandwich() {
}
}
-
Configure Application Class
Robolectric 默认会创建 manifest 中指定的Application类,如果需要替换为其他自定义,可以如下设置:
@Config(application = CustomApplication.class)
public class SandwichTest {
@Config(application = CustomApplicationOverride.class)
public void getSandwich_shouldReturnHamSandwich() {
}
}
Configure Resource Paths
自定义manifest,resource和assets目录的路径,如果你使用的是自定义build system的话,会需要用到。Config Properties
上述的配置既可以在@Config
注解中指定,也可以写在配置文件中。
Create a file namedrobolectric.properties
and make sure it can be found on the classpath.System Properties
android {
testOptions {
unitTests.all {
systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
systemProperty 'robolectric.dependency.repo.id', 'local'
}
}
}
- 关于Activity生命周期
创建一个MyAwesomeActivity
的实例,并调用 life cycle 的onCreate()
方法:
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().get();
- 测试Qualified Resources
resource qualifiers允许根据设备不同的语言、屏幕大小等情况加载不同的资源
values/strings.xml
<string name="not_overridden">Not Overridden</string>
<string name="overridden">Unqualified value</string>
<string name="overridden_twice">Unqualified value</string>
values-en/strings.xml
<string name="overridden">English qualified value</string>
<string name="overridden_twice">English qualified value</string>
values-en-port/strings.xml
<string name="overridden_twice">English portrait qualified value</string>
@Test
@Config(qualifiers="en-port")
public void shouldUseEnglishAndPortraitResources() {
final Context context = RuntimeEnvironment.application;
assertThat(context.getString(R.id.not_overridden)).isEqualTo("Not Overridden");
assertThat(context.getString(R.id.overridden)).isEqualTo("English qualified value");
assertThat(context.getString(R.id.overridden_twice)).isEqualTo("English portrait qualified value");
}
- Shadow Classes
Robolectric defines many shadow classes, which modify or extend the behavior of classes in the Android OS. When an Android class is instantiated, Robolectric looks for a corresponding shadow class, and if it finds one it creates a shadow object to associate with it. Every time a method is invoked on an Android class, Robolectric ensures that the shadow class' corresponding method is invoked first (if there is one), so it has a chance to work its magic. This applies to all methods, even static and final methods, because Robolectric is extra tricky!
2. Instrumented Unit Tests
在build.gradle
中配置测试依赖:
android {
...
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
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'
}
参考资料: