Android Studio下的Unit,UI测试(译)

在这篇文章中,简单介绍下如何在Android Studio中启动测试。书写一个单元测试并在你本地的开发机器上运行,一级如何在设备上做功能UI测试。

你将会学到:

  1. 更新Gradle配置文件来引入JUnit,Android测试依赖库;
  2. 写Unit测试,这将运行在你本地开发机器的Java虚拟机上;
  3. 写Espresso测试,浙江运行在手机设备或模拟器上。

环境:

  1. Android Studio:1.2+
  2. Android 4.0+ 测试机

具体怎么做呢?分为以下几步:

1. 创建一个Android Studio工程

如果你第一次启动Android Studio,从欢迎界面选择“Start a new Android Studio project”。如果已经有一个工程打开了,选择File—->New——>New Project。

这个“Create new project”向导会通过程序指导你。在第一个界面输入如下的内容:

创建工程

你可以不用管剩下的选项,用默认值即可。一直点下一步直到工程创建成功。

你可以点击运行按钮来检查app是不是正确运行了。

2. 为你的工程配置Unit测试支持

在你开始写测试之前,让我们通过一个小的清单来检查你的工程配置正确。

首先,在Build Variants面板下的Test Artifact一栏中确保你选择了“Unit Tests”。

接下来,创建文件夹test,test/java在你的模块(module)下的src文件夹。注意:你使用默认的Android视图将无法完成这一步。你既可以通过你系统的资源管理器去创建需要的文件夹,或者通过下拉左上角的Project面板切换到Project视图。最后,你的工程目录树看上去应该是这样:

最后,打开你模块(module)下的build.gradle(Module:app)文件,添加JUnit4到dependencies节点下,然后点击Gradle sync按钮。

build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
 testCompile 'junit:junit:4.12'
}

3. 创建你第一个Unit测试

既然万事俱备,是时候开始写你第一个测试代码了。但是为了做这些,你首先需要一些需要被测试的代码(不然测试什么)。出于这个动机,让我们创建一个非常简单的Calculator类,这个类将被称为我们的”class under test”。

我们将添加一些常用的运算操作方法,例如加法和减法。复制粘贴下面的代码到你的IDE中。不要担心这些方法未实现,仅仅让它们return 0就可以了。

Calculator.java

package com.example.testing.testingexample;

public class Calculator {

    public double sum(double a, double b){
        return 0;
    }

    public double subtract(double a, double b){
        return 0;
    }

    public double divide(double a, double b){
        return 0;
    }

    public double multiply(double a, double b){
        return 0;
    }
}

Android Studio提供了一个快捷的方式来给你创建一个具体的测试类。右键点击Calculator类,选择Go yo—>Test,最后选择”Create a new test…”

在打开的窗口上,选择JUnit 4与”setUp/@Before”选项,同样为我们所有的计算操作实现测试方法。

这将会实现一个测试类,里面包含待补充的测试方法,位于(app/src/test/java/com/example/testing/testingexample)。
这儿有一个你如何测试计算操作的例子。

CalculatorTest.java

package com.example.testing.testingexample;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class CalculatorTest {

    private Calculator mCalculator;

    @Before
    public void setUp() throws Exception {
        mCalculator = new Calculator();
    }

    @Test
    public void testSum() throws Exception {
        //expected: 6, sum of 1 and 5
        assertEquals(6d, mCalculator.sum(1d, 5d), 0);
    }

    @Test
    public void testSubtract() throws Exception {
        assertEquals(1d, mCalculator.subtract(5d, 4d), 0);
    }

    @Test
    public void testDivide() throws Exception {
        assertEquals(4d, mCalculator.divide(20d, 5d), 0);
    }

    @Test
    public void testMultiply() throws Exception {
        assertEquals(10d, mCalculator.multiply(2d, 5d), 0);
    }
}

4. 运行你的测试

最后一刻,运行你的测试代码。邮件点击CalculatorTest类,并选择Run—–>CalculatorTest。你也可以在你的工程目录下使用如下的命令在命令行运行:

./gradlew test

不管你采用何种方式运行你的测试代码,你都应该观察输出,告诉你4/4测试失败了。这儿有一个预期的结果,但是我们并没有实现具体的算数操作。

让我们修改下Calculator类中的这个方法: sum(double a,double b),让它返回正确的计算结果并重新运行测试代码。你讲会看到仅仅3/4测试失败了。

Calculator.java

public double sum(double a, double b){
    return a + b;
}

作为练习,你可以实现剩下的方法,让所有的测试都通过。

你可能注意到了Android Studio从没有请求连接到设备或者启动一个模拟器去运行这些测试代码。那是因为这部分测试代码位于src/tests文件夹下,这是本地单元测试运行在你计算机上的Java虚拟机上面。你写测试代码,然后实现这些函数让测试通过,接着添加一些新的更多的tests,这是一个快速循环地工作流,我们称之为测试驱动开发。
另一个需要意识到的是当在本地运行测试的时候Gradle在classpath中提供了你一个android.jar,其中包含Android framework类,但是它们并没有充分利用(你总不能调用Activity的方法作为例子并期待它们工作)。你应该使用一个模拟的框架,例如Mockito来模拟任何一个你需要使用的Android方法。
为了测试运行在设备上的这部分代码并且充分利用Android Framework,继续学习下部分内容。

5. 为你的工程配置instrumentation测试

虽然在Android framework中已经支持运行instrumentation测试,目前的开发工作集中在作为Android测试支持库的一部分发布的新的AndroidJUnitRunner。这个库也包含Espresso。什么是Espresso呢?一个运行UI功能测试的库。让我们通过修改当前模块(module)的build.gradle文件相关的节点来添加它们到我们的工程中。

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.example.testing.testingexample"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        //ADD THIS LINE:
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    //ADD THESE LINES:
    packagingOptions {
        exclude 'LICENSE.txt'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0' //← MAKE SURE IT’S 22.0.0
    testCompile 'junit:junit:4.12'

    //ADD THESE LINES:
    androidTestCompile 'com.android.support.test:runner:0.2'
    androidTestCompile 'com.android.support.test:rules:0.2'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'
}

重要的一点:由于一些dependency版本冲突,你可能不得不确保版本为22.0.0的库:com.android.support:appcompat-v7正被使用。同时,Android Studio可能会通知你Build Tools 22.0.1没有被安装。你应该接受建议的修复,这样Studio会安装正确的Build Tools。或者改变build.gradle中的这一行,让它指向你计算机安装过的版本。

接下来要做的就是在Build Variants面板中切换到Android Instrumentation Tests。你的工程应该在这个时候自动同步。如果没有的话,点击Gradle sync按钮。

6.为你的app添加简单的交互

在我们用Espresso开始UI测试之前,让我们添加一些简单的view以及交互动作到app。我们将使用一个EditText用来接受用户输入他的名字,以及一个Button来响应用户的输入,将结果输出到一个TextView中。打开res/layout/activity_main.xml然后粘贴下面的代码:

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>

添加onClick回调到MainActivity.java中:

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() + "!");
}

可以试着运行下app来看事件响应以及其他是否正确工作。在按Run 按钮之前,确保你的Run Configuration不是为了上一部分设置的。通过下拉菜单选择app。它看上去像下面这样:

7.创建并运行一个Espresso测试

在项目预览中,找到你的测试包(测试包的命名以androidTest结束),接着创建一个新的java类。你可以命名为MainActivityInstrumentationTest。粘贴下面的代码:

MainActivityInstrumentationTest.java

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()); //line 1

        onView(withText("Say hello!")).perform(click()); //line 2

        String expectedText = "Hello, " + STRING_TO_BE_TYPED + "!";
        onView(withId(R.id.textView)).check(matches(withText(expectedText))); //line 3
    }

}

这个类会被AndroidJUnitRunner运行,并执行测试实现方法sayHello()。下面是每一行的具体功能:

  1. 第一行,找到id为editText的view,并输入字符串”Peter”,接着关闭软键盘。
  2. 接着,测试代码执行点击带有”say hello!”内容的view。这是我们的按钮,我们在布局文件中没有设置id,所以我们通过它的内容找到它。
  3. 最后,TextView中的内容被拿来与期望的结果做比较,如果结果匹配那么测试将通过。

怎么运行这个测试呢?右键单击这个类,选择Run—->MainActivityInstrume…(第二个选项,带有Android图标)

This will run your test on the emulator or connected device and, if you watch the screen, you can see the actions (like typing into the EditText) being performed by the runner. See the output in Android Studio for a report on passed and failed tests.

这将会运行测试到模拟器或者连接的设备上,如果你观察屏幕,你会发现一些动作(比如在EditText输入的动作)被执行。观察Android Studio中的输出报告,查看是测试通过还是测试失败。