Android单元测试

参考

https://blog.csdn.net/qq_17766199/column/info/18260

https://blog.csdn.net/chaoyangsun/article/details/80095249

https://developer.android.com/studio/test

https://developer.android.com/training/testing/index.html

 

简介

用Android Studio创建工程的时候,

会自动加入测试依赖:

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

同时在src文件夹下会同时生成三个文件夹main、test、androidTest,

test和androidTest是专门针对源码级别的白盒测试的。

  • test

文件夹用于写不依赖设备环境的单元测试,即可在PC上直接运行;

该目录下的代码运行在本地JVM上,其优点是速度快,不需要设备或模拟器的支持,但是无法直接运行含有android系统API引用的测试代码。

 

  • androidTest

文件夹用于写需要在设备上才能运行的测试。

该目录下的测试代码需要运行在android设备或模拟器下面,因此可以使用android系统的API,速度较慢。

 

分类

在android测试框架中,常用的有以下几个框架和工具类:

  • JUnit4:Java最常用的单元测试框架
  • AndroidJUnitRunner:适用于 Android 且与 JUnit 4 兼容的测试运行器
  • Mockito:Mock测试框架
  • Espresso:UI 测试框架;适合应用中的功能性 UI 测试
  • UI Automator:UI 测试框架;适合跨系统和已安装应用的跨应用功能性 UI 测试

 

功能测试:和UI无关,测试IO操作、算法、流程等;

UI测试:测试UI交互逻辑,比如点击、登陆等。

 

Java单元测试框架:JUnit、Mockito等;

Android:AndroidJUnitRunner、Espresso等。

 

junit

就是上边自动导入的一个依赖:

testImplementation 'junit:junit:4.12'

 它是运行在本地JVM上,其优点是速度快,不需要设备或模拟器的支持,但是无法直接运行含有android系统API引用的测试代码。

 

JUnit 中的常用注解

注解名

含义

@Test

表示此方法为测试方法

@Before

在每个测试方法前执行,可做初始化操作

@After

在每个测试方法后执行,可做释放资源操作

@Ignore

忽略的测试方法

@BeforeClass

在类中所有方法前运行。此注解修饰的方法必须是static void

@AfterClass

在类中最后运行。此注解修饰的方法必须是static void

@RunWith

指定该测试类使用某个运行器

@Parameters

指定测试类的测试数据集合

@Rule

重新制定测试类中方法的行为

@FixMethodOrder

指定测试类中方法的执行顺序

 

执行顺序:@BeforeClass –> @Before –> @Test –> @After –> @AfterClass

 

 

Assert类

在JUnit 中核心类是Assert,Assert类中主要方法如下:

方法名

方法描述

assertEquals(String message, expected, actual)

断言传入的预期值与实际值是相等的

assertNotEquals(String message, unexpected, actual)

断言传入的预期值与实际值是不相等的

assertArrayEquals(String message, expecteds, actuals)

断言传入的预期数组与实际数组是相等的

assertNull(String message, Object object)

断言传入的对象是为空

assertNotNull(String message, Object object)

断言传入的对象是不为空

assertTrue(String message, boolean condition)

断言条件为真

assertFalse(String message, boolean condition)

断言条件为假

assertSame(String message, Object expected, Object actual)

断言两个对象引用同一个对象,相当于"=="

assertNotSame(String message, Object unexpected, Object actual)

断言两个对象引用不同的对象,相当于"!="

assertThat(String reason, T actual, Matcher<? super T> matcher)

断言实际值是否满足指定的条件

 

注:如果期望值和实际值和设置的断言相符,那么就表示测试通过,否则就会抛出一个断言异常。

 

基本用法

首先就是在test文件夹的某个包下创建一个测试类,

1.创建测试类

  • 可以直接创建,
  • 也可以在项目的某个类名上右键创建,如下图:

1选择要测试的方法 2点击确定 3选择测试类生成位置

最终生成的测试类:

注意:在方法上加入@Test注解,此方法才能被执行。

 

2.在对应方法中写入某个断言

比如:

assertEquals(4, 2 + 2);
assertNull(null);

直接写assertEquals等Assert中的方法,会自动把Assert静态导入到此测试类。

import static org.junit.Assert.*;

 

3.运行

右键,run,表示运行此测试类的所有被@Test注解的方法。

如果只想运行某个方法,则可以在某个方法上右键,run。

 

 

4.运行结果

  • 如果某个方法测试通过则在运行界面显示绿色对勾。
  • 如果测试不通过,则会有个x,并在右侧显示未通过原因/异常。

 

其他用法

@Test

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {

    /**
     * Default empty exception
     */
    static class None extends Throwable {
        private static final long serialVersionUID = 1L;

        private None() {
        }
    }

    /**
     * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed if
     * and only if an exception of the specified class is thrown by the method. If the Throwable's
     * message or one of its properties should be verified, the
     * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used instead.
     */
    Class<? extends Throwable> expected() default None.class;

    /**
     * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it
     * takes longer than that number of milliseconds.
     * <p>
     * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the
     * thread which runs the fixture's @Before and @After methods. This may yield different behavior for
     * code that is not thread safe when compared to the same test method without a timeout parameter.
     * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the
     * same thread as the fixture's @Before and @After methods.
     * </p>
     */
    long timeout() default 0L;
}

此注解是核心注解,可以不用参数直接用。

  • 如果想要测试是否抛出期望的异常,可以用expected这个参数,

  • 如果想要测试某个方法运行时间是否超时,可以用timeout参数。

 

期望和实际有误差

// 第一个参数:"sum(a, b)" 打印的tag信息 (可省略)

// 第二个参数: 3 期望得到的结果

// 第三个参数 result:实际返回的结果

// 第四个参数 0 误差范围(可省略)

assertEquals("sum(a, b)",3,result,0);

 

批量运行测试类

涉及注解@RunWith @Suite

 

创建两个测试类:

CalculaterTest.class,

CalculaterTest2.class

这两个类里边都写入了一些测试,

 

然后创建一个测试套件 SuiteTest ,以便将上面的类在一起运行:
@RunWith(Suite.class)
@Suite.SuiteClasses( { CalculaterTest.class, CalculaterTest2.class } )//被测试类
public class SuiteTest {
...
}

那么运行SuiteTest 就可以让CalculaterTest和CalculaterTest2一起运行。

 

参数化测试

Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试

它要满足下列要求:

  1. 用 @RunWith(Parameterized.class) 来注释 test 类
  2. 创建一个静态方法生成并返回测试数据,并注明@Parameters注解
  3. 创建一个公共的构造函数,接受存储上一条的测试数据
  4. 使用上述测试数据进行测试

 

@RunWith(Parameterized.class)
public class CalculaterTest3 {
    private int expected;
    private int a;
    private int b;

    @Parameters//创建并返回测试数据
    public static Collection params() {
        return Arrays.asList(new Integer[][] { { 3, 1, 2 }, { 5, 2, 3 } });
}

    //接收并存储(实例化)测试数据
    public CalculaterTest3(int expected, int a, int b) {
        this.expected = expected;
        this.a = a;
        this.b = b;
    }

    @Test
    public void sum() throws Exception {
        Calculater calculater = new Calculater();
        System.out.println("parameters : " + a + " + " + b);
        int result = calculater.sum(a, b);
        assertEquals(expected, result);
    }
}

 

@Rule用法

还记得一开始我们在@Before与@After注解的方法中加入"测试开始"的提示信息吗?

假如我们一直需要这样的提示,那是不是需要每次在测试类中去实现它。这样就会比较麻烦。

这时你就可以使用@Rule来解决这个问题,它甚至比@Before与@After还要强大。

自定义@Rule很简单,就是实现TestRule 接口,实现apply方法。

代码如下:

public class MyRule implements TestRule {

    @Override
    public Statement apply(final Statement base, final Description description) {

        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                // evaluate前执行方法相当于@Before
                String methodName = description.getMethodName(); // 获取测试方法的名字
                System.out.println(methodName + "测试开始!");

                base.evaluate();  // 运行的测试方法

                // evaluate后执行方法相当于@After
                System.out.println(methodName + "测试结束!");
            }
        };
    }
}

 

Assert.assertThat用法

上面我们所用到的一些基本的断言,如果我们没有设置失败时的输出信息,那么在断言失败时只会抛出AssertionError,无法知道到底是哪一部分出错。

而assertThat就帮我们解决了这一点。它的可读性更好。

assertThat(String reason, T actual, Matcher<? super T> matcher)

 

JUnit内置了很多Matcher实现,常用的匹配器:

匹配器

说明

例子

is

断言参数等于后面给出的匹配表达式

assertThat(5, is (5));

前边5是实际值,后边的是期望值。

not

断言参数不等于后面给出的匹配表达式

assertThat(5, not(6));

equalTo

断言参数相等

assertThat(30, equalTo(30));

equalToIgnoringCase

断言字符串相等忽略大小写

assertThat("Ab", equalToIgnoringCase("ab"));

containsString

断言字符串包含某字符串

assertThat("abc", containsString("bc"));

startsWith

断言字符串以某字符串开始

assertThat("abc", startsWith("a"));

endsWith

断言字符串以某字符串结束

assertThat("abc", endsWith("c"));

nullValue

断言参数的值为null

assertThat(null, nullValue());

notNullValue

断言参数的值不为null

assertThat("abc", notNullValue());

greaterThan

断言参数大于

assertThat(4, greaterThan(3));

lessThan

断言参数小于

assertThat(4, lessThan(6));

greaterThanOrEqualTo

断言参数大于等于

assertThat(4, greaterThanOrEqualTo(3));

lessThanOrEqualTo

断言参数小于等于

assertThat(4, lessThanOrEqualTo(6));

closeTo

断言浮点型数在某一范围内

assertThat(4.0, closeTo(2.6, 4.3));

allOf

断言符合所有条件,相当于&&

assertThat(4,allOf(greaterThan(3), lessThan(6)));

anyOf

断言符合某一条件,相当于或

assertThat(4,anyOf(greaterThan(9), lessThan(6)));

hasKey

断言Map集合含有此键

assertThat(map, hasKey("key"));

hasValue

断言Map集合含有此值

assertThat(map, hasValue(value));

hasItem

断言迭代对象含有此元素

assertThat(list, hasItem(element));

注:在导入时,导入 org.hamcrest.core包下边的Matcher实现。

自定义Matcher

这里我自定义一个字符串是否是手机号码的匹配器来演示一下。

只需要继承BaseMatcher抽象类,实现matches与describeTo方法。

代码如下:

public class IsMobilePhoneMatcher extends BaseMatcher<String> {

    /**
     * 进行断言判定,返回true则断言成功,否则断言失败
     */
    @Override
    public boolean matches(Object item) {
        if (item == null) {
            return false;
        }

        Pattern pattern = Pattern.compile("(1|861)(3|5|7|8)\\d{9}$*");
        Matcher matcher = pattern.matcher((String) item);

        return matcher.find();
    }

    /**
     * 给期待断言成功的对象增加描述
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("预计此字符串是手机号码!");
    }

    /**
     * 给断言失败的对象增加描述
     */
    @Override
    public void describeMismatch(Object item, Description description) {
        description.appendText(item.toString() + "不是手机号码!");
    }
}

 

AndroidJUnitRunner

https://blog.csdn.net/chaoyangsun/article/details/80163872

AndroidJUnitRunner,Google官方的android单元测试框架之一,适用于 Android 且与 JUnit 4 兼容的测试运行器!

AndroidJUnitRunner本质上不算是个测试工具,它只是Google基于JUnit 针对Anroid封装的一个测试用例运行器而已。至于它用来运行Espesso还是Uiautomator的用例都是可以的。

测试运行器可以将测试软件包和要测试的应用加载到设备、运行测试并报告测试结果。

此测试运行器的主要功能包括:

  • JUnit 支持
  • 访问仪器信息
  • 测试筛选
  • 测试分片

要求 Android 2.2(API 级别 8)或更高版本。

 

JUnit 支持

  • 测试运行器与 JUnit 3 和 JUnit 4(最高版本为 JUnit 4.10)测试兼容。
  • 使用时不要混用JUnit 3 和 JUnit 4 测试代码。
  • 如果要创建一个 JUnit 4 仪器测试类以在设备或模拟器上运行,则测试类必须以 @RunWith(AndroidJUnit4.class) 注解作为前缀。

 

如下是一个验证包名的JUnit 4 仪器:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.yangge.myapplication", appContext.getPackageName());
    }
}

 

访问仪器信息Instrumentation

可以使用 InstrumentationRegistry 类访问与测试运行相关的信息。

如 Instrumentation 对象:

    /**
     * Returns the instrumentation currently running. Use this to get an {@link Instrumentation}
     * into your test.
     *
     * @throws IllegalStateException if instrumentation hasn't been registered
     */
    public static Instrumentation getInstrumentation() {
        Instrumentation instance = sInstrumentationRef.get();
        if (null == instance) {
            throw new IllegalStateException("No instrumentation registered! "
                    + "Must run under a registering instrumentation.");
        }
        return instance;
    }
//使用时直接调用该静态方法即可:InstrumentationRegistry.getInstrumentation()

 

目标应用 Context 对象:

    /**
     * Return a Context for the target application being instrumented. Use this to get a
     * {@link Context} representing {@link Instrumentation#getTargetContext()} into your test.
     */
    public static Context getTargetContext() {
        return getInstrumentation().getTargetContext();
    }

测试应用 Context 对象InstrumentationRegistry.getContext()、传递到测试中的命令行参数InstrumentationRegistry.getArguments() 等。

使用 UI Automator 框架编写测试或编写依赖于 Instrumentation 或 Context 对象的测试时,此数据非常有用。

 

测试筛选

在 JUnit 4.x 测试中,您可以使用注解对测试运行进行配置。此功能可将向测试中添加样板文件和条件代码的需求降至最低。

除了 JUnit 4 支持的标准注解外,测试运行器还支持 Android 特定的注解,包括:

  • @RequiresDevice:指定测试仅在物理设备而不在模拟器上运行。
  • @SdkSupress:禁止在低于给定级别的 Android API 级别上运行测试。例如,要禁止在低于 18 的所有 API 级别上运行测试,请使用注解 @SDKSupress(minSdkVersion=18)。
  • @SmallTest、@MediumTest 和 @LargeTest:指定测试的运行时长以及运行频率。

 

下面是一个测试样例:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Before
    public void testBefore() throws Exception {
        LogUtil.e("JUnit","testBefore");
    }

    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        LogUtil.e("JUnit","testTest");
        assertEquals("com.yangge.myapplication", appContext.getPackageName());
    }

    @Test
    public void testTest() throws Exception {
        LogUtil.e("JUnit","testTest");
    }

    @Test
    @SmallTest
    public void testTestSmallTest() throws Exception {
        LogUtil.e("JUnit","testTestSmallTest");
    }

    @SmallTest
    public void testSmallTest() throws Exception {
        LogUtil.e("JUnit","testSmallTest");
    }

    @MediumTest
    public void testMediumTest() throws Exception {
        LogUtil.e("JUnit","testMediumTest");
    }

    @LargeTest
    public void testLargeTest() throws Exception {
        LogUtil.e("JUnit","testLargeTest");
    }

    @RequiresDevice
    public void testRequiresDevice() throws Exception {
        LogUtil.e("JUnit","testRequiresDevice");
    }

    @SdkSuppress(minSdkVersion = 19)
    public void testSdkSuppress() throws Exception {
        LogUtil.e("JUnit","testSdkSuppress");
    }

    @After
    public void testAfter() throws Exception {
        LogUtil.e("JUnit","testAfter");
    }
}

 

posted @ 2019-10-28 03:06  嘤嘤嘤123  阅读(1133)  评论(0编辑  收藏  举报