专栏首页潇涧技术专栏Head First Android Testing 1

Head First Android Testing 1

深入浅出Android测试教程 (1)

最近想写一个自己的库项目,以后开发都基于这个库项目来开发,于是乎,为了保证库项目中的代码功能没有问题,简单学了一些Android测试的内容,对于没有搞过测试的我来说,过程还是挺纠结的,现记录下来以备后用。

Android测试包含很多类型,例如Unit Tests,Instrumentation Tests以及各种其他的UI Tests等等。本次深入浅出教程只介绍前面两种测试,内容参考自android_testing_google_slides和Google Sample项目android-testing,感兴趣可以阅读示例感受下。

###第一部分 Unit Tests

Unit Test又叫JVM Tests 或者Local Tests,就是指直接运行在Java虚拟机而不是Dalvik虚拟机中的测试。

从1.1.0 RC1版本的Android Studio(Gradle插件从1.1版本)开始支持Unit Tests,使用方法教程可参考unit-testing-support

How it works?

Unit tests run on a local JVM on your development machine. Our gradle plugin will compile source code found in src/test/java and execute it using the usual Gradle testing mechanisms. At runtime, tests will be executed against a modified version of android.jar where all final modifiers have been stripped off. This lets you use popular mocking libraries, like Mockito.

①New source set test/ for unit tests ②Mockable android.jar ③Mockito to stub dependencies into the Android framework

测试步骤:

(1)配置build.gradle

apply plugin: 'com.android.application'
android {
    ...
    testOptions {
       unitTests.returnDefaultValues = true // Caution!
    }
}
dependencies {
    // Unit testing dependencies
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.10.19'
}

(2)配置Build Variants,选择Unit Tests

(3)编写Unit Test程序,放在src/test/java目录下

import android.content.Context;
import android.test.suitebuilder.annotation.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.Serializable;

import polaris.util.ObjectUtil;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

@RunWith(MockitoJUnitRunner.class)
@SmallTest
public class UnitTestSample {

    @Mock
    Context mMockContext; //Use MockitoJUnitRunner for easier initialization of @Mock fields.

    ObjectUtil objectUtil;
    private static final String fileName = "demo";

    @Before
    public void setUp() throws Exception {
        objectUtil = new ObjectUtil(mMockContext);
    }

    @Test
    public void testAdd() throws Exception {
        int result = 2 + 2;
        assertThat(result, is(4));
    }

    @Test
    public void testMod() throws Exception {
        int result = 7 % 3;
        assertThat(result, is(1));
    }

    @Test
    public void testAppName() throws Exception {//ok
        //can not find symbol class R
        //when(mMockContext.getString(R.string.app_name)).thenReturn("polaris");
        //assertThat(mMockContext.getString(R.string.app_name), is("polaris"));

        assertNotNull(mMockContext);
        assertNotNull(objectUtil);
    }

    @Test
    public void testSaveAndLoad() throws Exception {//fail
        User user = new User(1, "hujiawei");
        objectUtil.save(user, fileName);

        user = (User) objectUtil.load(fileName);
        assertThat(user.name, is("hujiawei"));
    }

    static class User implements Serializable {
        int id;
        String name;

        User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    public void tearDown() throws Exception {
    }

}

其中ObjectUtil类是一个用来保存和读取Object对象的工具类,并采用了Android Annotation注解注入Context。Android Annotation对EBean类的构造函数有个限制,要么不提供构造函数只用默认的构造函数,要么提供一个只包含参数Context的构造函数。

package polaris.util;

import android.content.Context;

import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;

import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * object tool
 * non singleton https://github.com/excilys/androidannotations/wiki/Enhance%20custom%20classes
 *
 * @author hujiawei
 * @date 15/5/30 09:41
 */
@EBean(scope = EBean.Scope.Default)
public class ObjectUtil {

    @RootContext
    Context context;

    //Error:(19, 1) error: @org.androidannotations.annotations.EBean annotated element should have only one constructor

//    public ObjectUtil() {
//
//    }

    public ObjectUtil(Context context) {
        this.context = context;
    }

    /**
     * save Object
     */
    public void save(Object data, String fileName) {
        File file = new File(context.getFilesDir(), fileName);
        if (file.exists()) {
            file.delete();
        }

        try {
            ObjectOutputStream oos = new ObjectOutputStream(context.openFileOutput(fileName, Context.MODE_PRIVATE));
            oos.writeObject(data);
            oos.close();
        } catch (Exception e) {//NPE
            e.printStackTrace();
        }
    }

    /**
     * load Object
     */
    public Object load(String fileName) {
        Object data = null;
        File file = new File(context.getFilesDir(), fileName);
        if (file.exists()) {
            try {
                ObjectInputStream ois = new ObjectInputStream(context.openFileInput(fileName));
                data = ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return data;
    }

}

(4)配置测试的运行参数

(5)运行测试有两种方式,可以简单地和运行普通程序一样点击Run按钮,结果会显示在下面的Run视图窗口中,也可以在终端运行./gradlew test,结果将放在/build/reports/test/debug/中,打开index.html文件即可。前者只运行当前测试的运行参数中配置的测试类和方法,而后者会检测整个项目中的所有Unit Test并进行测试。

上面四个测试中只有前三个是通过的,最后一个没能通过。(最后一个测试方法的问题出在ObjectOutputStream对象创建的时候,因为当前处于Unit Test中,没有设备或者模拟器所以没法直接写文件,对于这类特殊的测试就不能使用Unit Test,而是使用第二节中的Instrumentation Test,其中我们可以看到这个测试方法会通过的)

关于Running from Gradle

To run your unit tests, just execute the test task: ./gradlew test --continue. If there are some failing tests, links to HTML reports (one per build variant) will be printed out at the end of the execution.

[使用命令./gradlew test --continue可以运行Unit Test,如果有错可以在HTML报告文件中查看错误原因]

This is just an anchor task, actual test tasks are called testDebug and testRelease etc. If you want to run only some tests, using the gradle --tests flag, you can do it by running ./gradlew testDebug --tests='*.MyTestClass'.

[使用gradle --tests可以指定运行的测试类]

Because test is just a shorthand for "testDebug testRelease", the --continue flag is needed if you want to make sure all tests will be executed in all build combinations. Otherwise Gradle could stop after testDebug (failing tests cause the task to “fail”) and not execute testRelease at all.

[test是包含了testDebugtestRelease两部分测试的,如果不加上--continue并且testDebug出错了的话,testRelease便不会执行了]

关于问题”Method … not mocked.”

The android.jar file that is used to run unit tests does not contain any actual code - that is provided by the Android system image on real devices. Instead, all methods throw exceptions (by default). This is to make sure your unit tests only test your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked e.g. using Mockito). If that proves problematic, you can add the snippet below to your build.gradle to change this behavior:

android {
  // ...
  testOptions {
    unitTests.returnDefaultValues = true
  }
}

[文件android.jar中并不包含实际的代码,所有方法都只是空盒子,默认情况下都会抛出异常,这就使得你的Unit Test不会依赖于Android系统的某些特定行为,但是也会带来其他的问题(如果你没有使用显式地Mock的话),如果遇到这类问题可以尝试在builde.gradle文件中加上上面的配置修改原有的抛出异常的行为。]

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Head First Android Testing 2

    Instrumentation Tests又叫Device or Emulator Tests,即运行在设备或者模拟器上的测试。使用AndroidJunitRu...

    宅男潇涧
  • One Trip of building a Crawler

    最近需要从网上抓取大量的数据,于是体验了一下爬虫程序的开发和部署,主要是学会了一些实用工具的操作。

    宅男潇涧
  • Head First Stanford NLP (4)

    (深入浅出Stanford NLP 深入篇) 本文介绍与Stanford CoreNLP源码相关的内容。

    宅男潇涧
  • Hadoop学习之网络爬虫+分词+倒排索引实现搜索引擎案例

    本项目实现的是:自己写一个网络爬虫,对搜狐(或者csdn)爬取新闻(博客)标题,然后把这些新闻标题和它的链接地址上传到hdfs多个文件上,一个文件对应一个标题和...

    汤高
  • WIFI环境下Android手机和电脑通信

    前面已经写过一篇java实现最基础的socket网络通信,这篇和之前那篇大同小异,只是将客户端代码移植到手机中,然后获取本机IP的方法略有不同。 先讲一下本篇中...

    用户1215536
  • Java自动化测试框架-04 - TestNG之Test Method篇 - 道法自然,法力无边(详细教程)

    测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数。

    北京-宏哥
  • Spring Boot 之 Spring Data JPA 二 ( Query By Example)1 新建Spring Boot工程2 新建实体3 新建Repository4 新建一Service

    孙亖
  • RN项目第一节

    一、项目说明 本项目为模仿美团的项目,采用的是网上提供的API接口。导航采用 公司推荐的react-navigation,滚动条采用第三方组件react-na...

    谦谦君子修罗刀
  • python中的import,reloa

    import 作用: 导入/引入一个python标准模块,其中包括.py文件、带有__init__.py文件的目录。

    py3study
  • Java集成阿里云OSS

    创建用户完毕后,会得到一个 “AccessKey ID” 和 “AccessKeySecret”,然后复制这两个值到代码的 “AccessKey ID”和“Ac...

    乐心湖

扫码关注云+社区

领取腾讯云代金券