使用Android Studio做单元测试笔记

学会使用Android Studio的单元测试能为我们开发人员节省大量的时间,并且让代码变得更可靠并降低bug率,但在实际项目开发过程中,很少有开发主动写单元测试脚本和进行不包含UI界面的测试。其实我在写完这篇笔记之后也没有进行过单元测试,但是基本的单元测试方法还是值得开发人员掌握的。本文参照谷歌官方文档推荐的单元测试方法编写简单的单元测试脚本,实现不依赖UI界面的基于函数的单元测试,谷歌参考文档地址https://developer.android.com/studio/test/index.html

在网上搜了一下,使用Android Studio做单元的测试的文章还真是比较少,Eclipse倒是有一点,但是都是很早以前的文章,比较过时了,于是我去谷歌的官网读了一下谷歌最新的单元测试解决方案,把体会写在下面。

谷歌把单元测试分成了两种:Unit tests 和 Integration Tests,其中后者Integration Tests使用频率不高,本文主要讨论Unit tests

其中Unit tests又分成了Local Unit Tests 和 Instrumented unit tests;这两个是本文主要讨论的重点

下面来简单的看一下

Type Subtype Description
Unit tests
Local Unit Tests
本机单元测试
Unit tests that run on your local machine only. These tests are compiled to run locally on the Java Virtual Machine (JVM) to minimize execution time. Use this approach to run unit tests that have no dependencies on the Android framework or have dependencies that mock objects can satisfy.
这是设置起来最简单的一类测试,只能测试一些普通的java方法,不需要手机或者模拟器就可以进行测试,因为这种测试直接基于JVM虚拟机进行测试,包含安卓框架的类和对象不能用这种方法进行测试
Instrumented unit tests
设备单元测试
Unit tests that run on an Android device or emulator. These tests have access to Instrumentation information, such as the Context of the app you are testing. Use this approach to run unit tests that have Android dependencies which mock objects cannot easily satisfy.
这种测试需要让程序跑在安卓手机或者模拟器上(实际上不需要),如果你需要使用Context对象和安卓系统的依赖库,那么就需要使用这种测试,原理是建造一个虚拟的Context引用,并强制给其方法设置返回值
 
Local Unit Tests

先来看一下最简单的JVM单元测试部署方法,这种测试用来测试java代码,也就是你的代码里不能包含Context,Activity,Fragment等等一系列Android才有的类和库

测试步骤:

第一步:找到你项目的src文件夹,建立两个新文件夹src/test/java,在java这个目录里写一个.java文件作为测试用例,建立完毕后项目目录如下


第二步:在app的build.gradle文件中,添加如下两个依赖,其中junit:junit:4.12的依赖就是junit,注意这里是4而不是3,用起来会有一定差别,org.mockito:mockito-core:1.10.19是一个包含安卓环境的依赖

dependencies {
    // Required -- JUnit 4 framework
    testCompile 'junit:junit:4.12'
    // Optional -- Mockito framework
    testCompile 'org.mockito:mockito-core:1.10.19'
}

第三步:在刚刚的src/test/java/目录下的测试文件中写测试代码

注:相对于JUnit3,使用 JUnit 4的时候, 你不需要让你的测试类继承  junit.framework.TestCase类 . 你也不需要让你的测试函数以  ‘test’为前缀 , 也不需要用到  junit.framework  or  junit.extensions  包中的任何类.
下面是我写的一个简单的测试用例。一个是检测一个返回值为int的函数返回结果是否正确,一个是测试一个字符串是否以http:开头,如果两个函数都返回值正确那么测试成功,如果有任何失败那么测试失败
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Created by Alex on 2016/5/20.
 */
public class AlxTestCase {
    @Test
    public void test_algebra_Returns_Int() {
        assertEquals(add(2,3),5);
    }

    public int add(int a,int b){
        return a+b;
    }

    @Test
    public void test_Url_Returns_bool() {
        assertTrue(isUrl("https://baidu.com"));
    }

    public boolean isUrl(String a){
        return a.startsWith("http:");
    }

}
在这里讲一下assert函数的使用:

为了测试app的某一个组件有没有返回正确的期望值, 需要用到 junit.Assert的方法 来进行检测 ,它主要进行的是比较两个参数是否完全相等,其中一个参数是用待测试代码返回的参数,另一个参数是你期望得到的正确返回值如果,两个参数一致,那么测试通过,否则不通过。

上面代码中的assertEquals(int,int);就是比较左右两个整形数是否相等,如果相等就代表测试成功,第一个int是函数返回值,第二个int是我们已经提前算好的正确值,这里的参数还可以传入两个long,或者两个float等等

assertTrue(boolean),里面的参数是函数的返回值,如果函数返回为true则测试通过,如果返回为false则测试失败

第四步:启动执行测试类

  1. 把屏幕左侧的目录树切换到project卡,右击 project 的顶端节点选择 synchronize ‘项目名’.
  2. 还是在 Project 卡下, 右击刚刚写好的测试类并且选择run按钮,如下图
第五步:观察测试结果

如果运行完毕后显示结果如下,说明所有测试都成功,并且还会提示出每个方法执行的时间,这是个很实用的功能,能帮我们研究哪些方法是耗时的


如果有一个或者多个测试函数没有测试成功,比如我们把测试的url改成https://baidu.com
效果如下



Instrumented unit tests

上面讲了没有Android框架的时候对简单的java方法进行测试,那么如果我要测试带有Context,SharedPreference这样的就需要使用Instrumented unit test了,下面用一个实例演示一下

第一步,第二步与LocalUnitTest相同

我们知道context有一个getString()方法,参数是一个strings.xml里面的资源ID,现在我们要用一个虚拟的Context去调用getString()方法,但是strings.xml并没有被编译,那么如何使用context.getString(R.id.xxx)的值呢

import org.junit.Test;

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

import android.content.Context;


/**
 * Created by Alex on 2016/5/20.
 */
@RunWith(MockitoJUnitRunner.class)
public class AlxTestCase {
    private static final String FAKE_STRING = "HELLO WORLD";

    @Mock
    Context mMockContext;

    @Test
    public void readStringFromContext_LocalizedString() {
        // 因为mMockContext是一个被虚拟出来的的假Context,只能起一个不为null的作用
        /**
         * 下面这句话非常重要,作用是如果我用这个假的Context去拿写在strings.xml的一个String,因为Context是假的,
         * strings.xml也没有被编译,所以mMockContext.getString(R.string.uploadmenu)返回值是个null
         * 下面这句话就是当执行mMockContext.getString(R.string.uploadmenu)的时候,强制其返回值是 HELLO WORLD
         */
        when(mMockContext.getString(R.string.uploadmenu)).thenReturn(FAKE_STRING);
//        Log.i("Alex","测试开始"+mMockContext.getString(R.string.uploadmenu));// 像Log,Toast函数是不能被虚拟的

        // 如果不执行上面when(...)那一句,result是null,但是有那一句,return 就变成了Hello World
        String result = mMockContext.getString(R.string.uploadmenu);
        // 检测result是否等于“hello world”
        assertThat(result, is("HELLO WORLD"));
    }

}

从代码上可以看出,一个空的引用mMockContext加上@Mock修饰以后,就变成了一个虚拟的Context引用,可以调用context的方法,但是返回值和真实情形下有差别(虚拟的返回为null),为了消除这种差别,使用了 when(表达式)   thenReturn(该表达式应该返回的值)语句,通过 这种方式来“写死”某个函数的返回值,跳过这些和逻辑无关的代码向后执行

同理,mMockContext.getSharedPreferences("mySP",Context.MODE_PRIVATE)的返回也是null

那么如果要用SharedPreference怎么办呢,和Context一样,可以在SharedPreference的引用上加@Mock,这样一个虚拟的SharedPreference就有了,下面是示例

import org.junit.Test;

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

import android.content.Context;
import android.content.SharedPreferences;



/**
 * Created by Alex on 2016/5/20.
 */
@RunWith(MockitoJUnitRunner.class)
public class AlxTestCase {
    private static final String FAKE_STRING = "HELLO WORLD";

    @Mock
    Context mMockContext;
    @Mock
    SharedPreferences mMockSP;

    @Test
    public void readIntFromSharedPreference() {
//        SharedPreferences sp = mMockContext.getSharedPreferences("mySP",Context.MODE_PRIVATE);//这里的sp也是null
        mMockSP.getInt("myInt",100);//此处返回值是0
        when(mMockSP.getInt("myInt",100)).thenReturn(5);//现在返回值变成5
        assertThat(mMockSP.getInt("myInt",100), is(5));
    }

}

同样还是使用的when(表达式) thenReturn(该表达式应该返回的值)语句实现的给表达式结果强制赋值

如果执行正常,返回结果同Local unit test 相同


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值