Android测试

2017/2/27 15:49 下午 posted in  Android  

Android的测试基于JUnit。

测试种类

  • Local unit tests
    目录:module-name/src/test/java/。
    运行于本地JVM。测试文件中仅包含java代码,不使用Android相关API,或使用到的API可通过mock对象代替。
  • Instrumented tests
    目录:module-name/src/androidTest/java/。
    运行于模拟器或真机。测试文件中需要使用Android相关API。系统会打包一个测试apk和一个实际apk,这2个apk运行在同一个进程。

test-types

大类别 子类别 描述
单元测试(Unit tests) Local Unit Tests 运行于本地JVM,不依赖Android Framework或使用mock对象
Instrumented Unit Tests 运行于模拟器或真机,可以使用与设备相关的信息,如app的Context。用于需依赖Android Framework或mock对象无法满足时
集成测试(Integration tests) app内组件测试 验证app的行为与预期是否一致。可使用UI测试框架如Espresso
跨app组件测试 验证用户app间或用户app与系统app间的行为。可使用UI测试框架如UI Automator

Unit Testing

  • Robolectric
  • JUnit

Instrumentation Testing

  • Espresso
  • UIAutomator
  • Google Android Testing
  • Robotium
  • Selendroid

本地单元测试(Local Unit Tests)

目录:module-name/src/test/java/

JUnit

只可测试Java代码,如果需要依赖Android API,可以使用Mockito库来mock对象。

JUnit4与JUnit3相比,测试类不再需要继承自junit.framework.TestCase,测试方法不再需要“test”前缀,也不再需要使用junit.framework或junit.extensions包下的类。

使用步骤

1. 添加依赖

在build.gradle(Module:app)中添加JUnit4依赖。

testCompile 'junit:junit:4.12'
// 可选
// testCompile 'org.mockito:mockito-core:1.10.19'
2. 创建测试类

光标置于类名上,Option+Enter,Create Test,选择JUnit4和“setUp/@Before”,勾选要生成的测试方法。

3. 编写测试

使用JUnit框架提供的断言来编写自己的测试。

import org.junit.Test;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
    
public class EmailValidatorTest {
    
   @Test
   public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
       assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
   }
   ...
}
4. 运行测试

有以下几种方法:

  • 运行单个测试文件,在Project窗口下,右键测试文件,点击Run;
  • 运行测试文件中的某些方法或所有方法,右键要运行的方法,点击Run(快捷键Ctrl+R);
  • 运行某个目录下的所有测试,右键该目录,选择Run tests;
  • 命令行./gradlew test。

mock Android依赖

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import android.content.SharedPreferences;

@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

Mock了部分Android的API,可以直接在JVM上调用Android相关类、方法,可以使用Context。

设备单元测试(Instrumented Unit Tests)

目录:module-name/src/androidTest/java/

测试支持库(Testing Support Library)包含JUnit 4 test runner(AndroidJUnitRunner)和UI测试API(Espresso和UI Automator)。

使用步骤

1. 配置依赖

在Project的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库的compile依赖和espresso-core库的androidTestCompile依赖,可能会因依赖冲突导致build失败,可进行如下配置:

androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
   exclude group: 'com.android.support', module: 'support-annotations'
})

在module的gradle配置中添加如下配置,以指定AndroidJUnitRunner作为默认的Test Instrumentation Runner。

android {
   defaultConfig {
       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}

2. 创建测试类

在测试类前添加@RunWith(AndroidJUnit4.class)注解。

import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
    
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LogHistoryAndroidUnitTest {
    
   public static final String TEST_STRING = "This is a string";
   public static final long TEST_LONG = 12345678L;
   private LogHistory mLogHistory;
    
   @Before
   public void createLogHistory() {
       mLogHistory = new LogHistory();
   }
    
   @Test
   public void logHistory_ParcelableWriteRead() {
       // Set up the Parcelable object to send and receive.
       mLogHistory.addEntry(TEST_STRING, TEST_LONG);
    
       // Write the data.
       Parcel parcel = Parcel.obtain();
       mLogHistory.writeToParcel(parcel, mLogHistory.describeContents());
    
       // After you're done with writing, you need to reset the parcel for reading.
       parcel.setDataPosition(0);
    
       // Read the data.
       LogHistory createdFromParcel = LogHistory.CREATOR.createFromParcel(parcel);
       List<Pair<String, Long>> createdFromParcelData = createdFromParcel.getData();
    
       // Verify that the received data is correct.
       assertThat(createdFromParcelData.size(), is(1));
       assertThat(createdFromParcelData.get(0).first, is(TEST_STRING));
       assertThat(createdFromParcelData.get(0).second, is(TEST_LONG));
   }
}

3. 创建测试集(Create a test suite)

测试集将多个测试组织在一起,测试集置于测试包中(test package),包名一般以.suite结尾。

import com.example.android.testing.mysample.CalculatorAddParameterizedTest;
import com.example.android.testing.mysample.CalculatorInstrumentationTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

// Runs all unit tests.
@RunWith(Suite.class)
@Suite.SuiteClasses({CalculatorInstrumentationTest.class,
        CalculatorAddParameterizedTest.class})
public class UnitTestSuite {}

4. 运行测试

有以下几种方法:

  • 运行单个测试文件,在Project窗口下,右键测试文件,点击Run;
  • 运行测试文件中的某些方法或所有方法,右键要运行的方法,点击Run(快捷键Ctrl+R);
  • 运行某个目录下的所有测试,右键该目录,选择Run tests;

UI测试

目录:module-name/src/androidTest/java/

单APP UI测试(Espresso)

使用步骤

1. 添加依赖

在build.gradle(Module:app)中添加。

...
android {
   ...
   defaultConfig {
       ...
       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}
    
dependencies {
   ...
   androidTestCompile('com.android.support.test.espresso:espresso-core:2.2') {
       // Necessary if your app targets Marshmallow (since Espresso
       // hasn't moved to Marshmallow yet)
       exclude group: 'com.android.support', module: 'support-annotations'
   }
   androidTestCompile('com.android.support.test:runner:0.3') {
       // Necessary if your app targets Marshmallow (since the test runner
       // hasn't moved to Marshmallow yet)
       exclude group: 'com.android.support', module: 'support-annotations'
   }
}

2. 创建测试类

在src/androidTest/java目录下添加测试类,测试类的包最好与相应的类的包保持一致。

3. 编写测试

示例如下:

// MainActivityInstrumentationTest.java
    
import static android.support.test.espresso.Espresso.onView;
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;
    
// Tests for MainActivity
public class MainActivityInstrumentationTest {
    
   // Preferred JUnit 4 mechanism of specifying the activity to be launched before each test
   @Rule
   public ActivityTestRule<MainActivity> activityTestRule =
           new ActivityTestRule<>(MainActivity.class);
    
   // Looks for an EditText with id = "R.id.etInput"
   // Types the text "Hello" into the EditText
   // Verifies the EditText has text "Hello"
   @Test
   public void validateEditText() {
       onView(withId(R.id.etInput)).perform(typeText("Hello")).check(matches(withText("Hello")));
       // 上面使用了很多静态引用,以保证代码易读性。
       // 如果不使用静态引用,代码如下:
       Espresso.onView(ViewMatchers.withId(R.id.etInput))
           .perform(ViewActions.typeText("Hello"))
           .check(ViewAssertions.matches(ViewMatchers.withText("Hello")));
   }
}

onView、perform、check方法的返回类型均为ViewInteraction。

Espresso测试的标准模式是查找一个View(ViewMatchers),在该View上做一些事情(ViewActions),然后验证该View的一些属性(ViewAssertions)。

4. 运行测试

  • 使用Android Studio运行单个测试:右键测试类,选择Run,然后在控制台中查看结果。
  • 使用gradle运行所有测试:打开Gradle Window,在Tasks->verification下找到connectedDebugAndroidTest,右键选中然后选择Run。会在以下位置生成测试报告app/build/reports/androidTests/connected/index.html。

多APP UI测试(UI Automator)

暂略。

参考

Getting Started with Testing
Building Local Unit Tests
Building Instrumented Unit Tests
Testing UI for a Single App
Testing UI for Multiple Apps

在Android Studio中进行单元测试和UI测试
Android Testing Options
Unit Testing with Robolectric
UI Testing with Espresso
Robolectric vs Android Test Framework
蘑菇街支付金融Android单元测试实践