menu

JUnit4

断言

JUnit4 只提供了一些基本的断言给基本类型、对象、数组

  • assertArrayEquals;
  • assertEquals;
  • assertFalse;
  • assertNotNull;
  • assertNotSame;
  • assertNull;
  • assertSame;
  • assertThat;
  • assertTrue;
Matcher

junit 提供 assertThat 断言,通过 Matcher 实现更灵活语义更自然的断言。

assertThat([value], [matcher statement]);:

assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));
Hamcrest

Matcher 并不是 JUnit 的类,而是属于 Hamcrest

Hamcrest 提供很多的便利方法,详见 Wiki

junit4 也通过 JUnitMatchers 提供一些常用的 Matcher。

Matcher 是一个泛型接口,有一个 matches 方法,判断传入对象是否与当前 Matcher 匹配:

public interface Matcher<T> extends SelfDescribing {
    ...
    boolean matches(Object item);

assertThat(x, is(3)) 为例,Is 是一个简单的 Matcher 只是起到一个装饰作用,Is 里面包有一个实际的 Matcher,真正的判断有这个 Matcher 来做。

比如:

@Factory
public static <T> Matcher<T> is(T value) {
    return is(equalTo(value));
}

创建了一个 IsEqual Matcher,IsEqual 通过构造函数传入期望值,在 matches 方法和实际值比对是否相等并返回。

Test runner

用来运行测试和显示测试结果

Runner 的 run 方法,当一个测试的状态发生变化是可以得到通知,Runner 可以根据这些通知更新 ui 或结果集。

/**
 * Run the tests for this runner.
 *
 * @param notifier will be notified of events while tests are being run--tests being
 * started, finishing, and failing
 */
public abstract void run(RunNotifier notifier);

通过类注解 @RunWith(Runner) 来指明这个测试类的 Runner

Suite

Suite 也是一个 Test runner,用于跑多个单元测试类

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
  TestFeatureLogin.class,
  TestFeatureLogout.class,
  TestFeatureNavigate.class,
  TestFeatureUpdate.class
})

public class FeatureTestSuite {
  // the class remains empty,
  // used only as a holder for the above annotations
}

Rules

通过注解将一个对象标记为 TestRule,该对象须实现 TestRule 接口, TestRule 接口定义了如下方法:

Statement apply(Statement base, Description description);

Statement 我理解为表示一个测试动作,相当于一个 Test 方法的执行。调用 Statement#evaluate 表示执行测试。

TestRule 会将 Statement 重新包一层,实现在在测试执行之前或之后插入自己的代码,比如:

public abstract class ExternalResource implements TestRule {
    public Statement apply(Statement base, Description description) {
        return statement(base);
    }

    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }
    ...
}

具体 junit4 的 rules 见: Rules · junit-team/junit4 Wiki

**androidTest 的 Rule 好像不起作用**

异常测试(Exception)

通过注解断言方法会抛出异常

@Test(expected = IndexOutOfBoundsException.class) 
public void empty() { 
     new ArrayList<Object>().get(0); 
}

也可以通过直接的 try-catch

@Test
public void testExceptionMessage() {
    try {
        new ArrayList<Object>().get(0);
        fail("Expected an IndexOutOfBoundsException to be thrown");
    } catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
        assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
    }
}

用 ExpectedException Rule 更灵活功能更强

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
    List<Object> list = new ArrayList<Object>();

    thrown.expect(IndexOutOfBoundsException.class);
    thrown.expectMessage("Index: 0, Size: 0");
    list.get(0); // execution will never get past this line
}

超时

同时设置超时,可以测试一个方法的执行时间

@Test(timeout=1000)
public void testWithTimeout() {
  ...
}

可以通过 Timeout Rule 设置每个方法的超时时间

@Rule
public Timeout globalTimeout = Timeout.seconds(10); // 10 seconds max per method tested

忽略一个测试

可以通过注解 @Ignore 忽略一个测试

@Ignore("Test is ignored as a demonstration")
@Test
public void testSame() {
    assertThat(1, is(1));
}

测试执行顺序

JUnit4 可以通过注解 @FixMethodOrder 测试方法的执行顺序

参数化测试

预先定义好一系列的注入参数,注入到测试的构造函数或字段,来允许测试。

需要 Test runner Parameterized

方法注解 @Parameters 用于生成参数

@Parameters
public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][] {
             { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
       });
}

默认情况下会注入到构造函数的参数列表:

public FibonacciTest(int input, int expected) {
    fInput= input;
    fExpected= expected;
}

也可以通过 @Parameter 注入到字段

@Parameter // first data value (0) is default
public /* NOT private */ int fInput;

@Parameter(value = 1)
public /* NOT private */ int fExpected;

具体见 Parameterized tests · junit-team/junit4 Wiki

假设

Assumptions with assume · junit-team/junit4 Wiki

Android Studio 2.1.0 Gradle 2.10 com.android.tools.build:gradle:2.1.0

Local Unit Tests

与 Android 环境无关的,在本地(PC)环境的 Java 虚拟机内运行的测试。

source sets src/test/

要使用 junit4 需在模块build.gradle 加入依赖,官方文档写的是 app’s top-level build.gradle ,我理解成项目的根目录,被坑了不少时间,实际上应该是 app 模块的根目录。

dependencies {
    testCompile 'junit:junit:4.12'
}

Instrumented Tests

source sets src/androidTest/

要使用 junit4 需在模块build.gradle 加入依赖

dependencies {
    androidTestCompile 'junit:junit:4.12'
}

unit

ui

app component integration testing

Test Rule

Test Runner

需要在 build.gradle 注明:

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

类注解 @RunWith(AndroidJUnit4.class) 这个是可选的,移除掉也能正常允许

Robolectric

在 jvm 模拟一个 Android 的核心运行环境。解决跑 Instrumented test 需要编译 apk 到 android 设备上运行的问题。

keyboard_arrow_up