Android中的单元测试(你有用过吗?O(∩_∩)O~)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/vv_bug/article/details/53151737

前言: 周末真的是除了睡觉还是睡觉啊O(∩_∩)O~,打开博客,看到别人大牛写的东西的时候,感觉差距好大啊,自己要学习的东西太多太多了,不管怎样,现在还是加油吧,骚年~~

对Android Studio还不是很熟的,或者是ADT的深度中毒患者的可以去看看这篇文章
因为之前一直用的ADT,才转到AS不久,所以对AS不是很熟悉,每次创建完工程后,as会自动的在src目录中会自动创建两个目录:

这里写代码片

没用过单元测试的你是不是跟我一样困惑,这两货是什么鬼?
没错~这就是我们今天所研究的东西(单元测试)

那么单元测试有什么好处呢?
1、比如你做“登录”模块,但在用户登录之前,你需要干很多东西,走很久才走到你登录模块,这样测试起来每次花在走前面流程的时间就浪费的很多了,有了单元测试,你可以直接跳过之前所有步骤,直接走登录流程。
2、可以很容易的发现你程序中的bug,通过单元测试设置一个预期值,当跟你程序中的结果值不一致的时候,就说明你的逻辑代码有问题。

那有的童鞋会说了,你说的第一点我也可以做到啊,我直接修改下代码,让app一启动直接跳转到我指定的页面,不就可以绕开前面的逻辑了?我想说,你硬是要这样的话,我也没办法啊,(^__^) ,单元测试不需要你改之前的逻辑,你只需要针对你需要测试的部分做测试就可以了,不会对你app造成影响。

下面说下src下androidTest跟src下的test的区别:

src/test src/androidTest
位于src/tests目录下的测试是运行在本地电脑Java虚拟机上的单元测试。编写测试,实现功能使测试通过,然后再添加更多的测试…这种工作方式使快速迭代成为可能,我们称之为测试驱动开发, 跟src/test不同的是运行在设备上,并充分利用Android框架的测试

src/test不需要你连接设备,而src/androidTest需要连接设备测试。

一、先说一下src/tests
首先我们需要添加依赖:

dependencies {
    testCompile 'junit:junit:4.12'
}

然后我们创建一个计算的工具类:
package com.cisetech.androidunit;

/**
* author:yinqingy
* date:2016-11-13 21:00
* blog:http://blog.csdn.net/vv_bug
* desc:
*/

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

public double substract(double a, double b){
    return a-b;
}

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

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

}

这个时候呢,既然是计算的工具类,你肯定需要验证一下这些计算的到底对不对了。
接下来我们就针对Calculator 做一下测试,我们可以直接创建测试类,如:

as也为我们提供了快捷创建测试类的方法:
这里写图片描述

然后一路点击ok就可以了:
这里写图片描述

然后as自动会为我们对应创建一个测试类:

package com.cisetech.androidunit;

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

/**
 * author:yinqingy
 * date:2016-11-13 21:08
 * blog:http://blog.csdn.net/vv_bug
 * desc:
 */
public class CalculatorTest {
    @Before
    public void setUp() throws Exception {

    }

    @Test
    public void sum() throws Exception {

    }

    @Test
    public void substract() throws Exception {

    }

    @Test
    public void divide() throws Exception {

    }

    @Test
    public void multiply() throws Exception {

    }

}

@Before注解的意思是:在测试开始之前回调的方法( 比如说初始化我们的类)。
@Test注解的意思是:我们需要测试的方法(进行+-*/操作)。
了解用法之后,我们改改代码:

package com.cisetech.androidunit;

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 * author:yinqingy
 * date:2016-11-13 21:08
 * blog:http://blog.csdn.net/vv_bug
 * desc:
 */
public class CalculatorTest {
    private Calculator mCalculator;

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

    @Test
    public void testSum() throws Exception {
        assertEquals("1+5=6",6d, mCalculator.sum(1d, 5d), 0);
    }

    @Test
    public void testSubstract() throws Exception {
        assertEquals("5-4=1",2d, mCalculator.substract(5d, 4d), 0);
    }

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

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

assertEquals方法可以传入一个预期的值、我们算出的值、预期的值跟算出值的误差范围。

终于到运行测试的时候了!右键点击CalculatorTest类,选择Run > CalculatorTest。也可以通过命令行运行测试,在工程目录内输入:

./gradlew test

这里写图片描述

通过结果我们可以看到,我们的testSum跟testDivide方法通过了测试,而另外两个方法没有通过测试:
错误很明显哦

java.lang.AssertionError: 5-4=1 
Expected :2.0
Actual   :1.0

通过这个小实验,是不是对junit有点感兴趣了呢?我们也只是使用了其冰山一角啊,其它的一些用法,小伙伴们就自己去研究吧。

前面我们看了src/test,下面看看src/androidTest的用法(先看看我们的demo运行后的效果图):
这里写图片描述
没错!!就是一个简单的三级联动的实现。

demo中是直接导的一个数据库文件。
city.db数据库文件结构(大致看一下哈):

city表结构:
city表结构

district表结构:

这里写图片描述

province表结构:
这里写图片描述

看不懂的小伙伴也没关系,我待会会解释一下的,sqlite可视化工具我也会上传在demo中O(∩_∩)O~

明确下我们的目标:

package com.cisetech.androidunit.dao;

import com.cisetech.androidunit.bean.AddressBean;

import java.util.List;

/**
 * author:yinqingy
 * date:2016-11-13 21:52
 * blog:http://blog.csdn.net/vv_bug
 * desc:地址数据访问层
 */

public interface AddressDao {
    /**
     * 关闭数据库
     */
    void closeDb();

    /**
     * 根据城市Code找到所有的地区
     * @param cityCode
     * @return
     * @throws Exception
     */
    List<AddressBean> getDistirctsByPcode(String cityCode) throws Exception;

    /**
     * 根据省份code找到所有的市
     * @param pCode
     * @return
     * @throws Exception
     */
    List<AddressBean> getCitiesByPcode(String pCode) throws Exception;

    /**
     * 获取所有的省份
     * @return
     * @throws Exception
     */
    List<AddressBean> getAllProvince() throws Exception;
}

然后写好实现类:

package com.cisetech.androidunit.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import com.cisetech.androidunit.R;
import com.cisetech.androidunit.bean.AddressBean;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * author:yinqingy
 * date:2016-11-13 21:54
 * blog:http://blog.csdn.net/vv_bug
 * desc:地址数据访问层实现层
 */

public class AddressDaoImp implements AddressDao {
    public static final String tag = "AddressDao";
    public static final String CITY_DB_NAME = "CITY_DB";
    private SQLiteDatabase db;
    public AddressDaoImp(Context context) {
        File db_file = null;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 判断sd卡是否有效
            db_file = new File(Environment.getExternalStorageDirectory(),CITY_DB_NAME);
            Log.i(tag, "拿到内存卡中的city——db");
        }
        if (db_file == null) {// 内存卡不存在的情况(放在手机缓存中)
            Log.i(tag, "拿到手机内存中的city——db");
            db_file = new File(context.getFilesDir(),CITY_DB_NAME);
        }
        if (db_file != null && db_file.length() > 0) {
            db = SQLiteDatabase.openDatabase(db_file.getAbsolutePath(), null,
                    SQLiteDatabase.OPEN_READONLY);
        }else{
            try {
                InputStream is = context.getResources().openRawResource(R.raw.city);
                FileOutputStream fos = new FileOutputStream(db_file);
                byte[] buffer = new byte[512];
                int count = 0;
                while ((count =is.read(buffer)) > 0) {
                    fos.write(buffer, 0, count);
                    fos.flush();
                }
                fos.close();
                is.close();
                Log.i(tag, "db拷贝完成");
                db = SQLiteDatabase.openDatabase(db_file.getAbsolutePath(), null,
                        SQLiteDatabase.OPEN_READONLY);
            } catch (Exception e) {
                db_file.delete();//异常就删除db文件
                e.printStackTrace();
                Log.i(tag, "db拷贝异常");
            }
        }
        if (db == null) {
            Log.i(tag, "获取db异常~~~~");
        }
    }

    @Override
    public void closeDb() {
        if (db != null) {
            db.close();
            db = null;
        }
    }

    /**
     * 根据城市code获取所有的district
     * @return
     */
    public List<AddressBean> getDistirctsByPcode(String cityCode) {
        List<AddressBean> cities = new ArrayList<AddressBean>();
        try {
            Cursor cursor = db.rawQuery("select * from district where pcode=?",
                    new String[] { cityCode });
            while (cursor.moveToNext()) {
                AddressBean item = new AddressBean();
                item.setCode(cursor.getString(cursor.getColumnIndex("code")));
                byte bytes[] = cursor.getBlob(2);
                String name = new String(bytes, "gbk");
                if(!TextUtils.isEmpty(name)){
                    name=name.trim();
                }
                item.setName(name);
                cities.add(item);
            }
            cursor.close();
        } catch (Exception e) {
            return cities;
        }
        return cities;
    }
    /**
     * 根据省获取city
     *
     * @return
     */
    @Override
    public List<AddressBean> getCitiesByPcode(String pCode) throws Exception {
        List<AddressBean> cities = new ArrayList<AddressBean>();
        try {
            Cursor cursor = db.rawQuery("select * from city where pcode=?",
                    new String[] { pCode });
            while (cursor.moveToNext()) {
                AddressBean item = new AddressBean();
                item.setCode(cursor.getString(cursor.getColumnIndex("code")));
                byte bytes[] = cursor.getBlob(2);
                String name = new String(bytes, "gbk");
                if(!TextUtils.isEmpty(name)){
                    name=name.trim();
                }
                item.setName(name);
                cities.add(item);
            }
            cursor.close();
        } catch (Exception e) {
            return cities;
        }
        return cities;
    }

    /**
     * 获取所有的province
     * @return
     */
    @Override
    public List<AddressBean> getAllProvince() {
        List<AddressBean> provinces = new ArrayList<AddressBean>();
        try {
            Cursor cursor = db.rawQuery("select * from province", null);
            while (cursor.moveToNext()) {
                AddressBean item = new AddressBean();
                item.setCode(cursor.getString(cursor.getColumnIndex("code")));
                byte bytes[] = cursor.getBlob(2);
                String name = new String(bytes, "gbk");
                if(!TextUtils.isEmpty(name)){
                    name=name.trim();
                }
                item.setName(name);
                provinces.add(item);
            }
            cursor.close();
        } catch (Exception e) {
            return provinces;
        }
        return provinces;
    }
}

在还没有把写ui布局逻辑的时候,我们先测试一下我们的daoimp的正确性:

我们直接在src/androidTest下创建一个AddressDaoImpTest的文件,然后把ExampleInstrumentedTest的代码copy过去:

 @Test
    public void testDao() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        String name = appContext.getPackageName();
        assertEquals("com.cisetech.androidunit", appContext.getPackageName());
        AddressDaoImp dao=new AddressDaoImp(appContext);
        List<AddressBean> allProvince = dao.getAllProvince();
        for (AddressBean bean:allProvince) {
            Log.e("TAG","province--->"+bean.getName());
        }
    }

我们运行代码,此时需要连上真机或者模拟器测试了(我们直接右击方法名运行,同时你也可以选择debug运行):

然后看到log里面打印:

1-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->四川省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->贵州省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->云南省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->西藏
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->陕西省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->甘肃省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->青海省
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->宁夏
11-14 23:07:31.128 27711-27741/com.cisetech.androidunit E/TAG: province--->新疆

是不是so easy!!

同时你还可以进行省市区的测试,我就不再运行测试了,留给小伙伴自己研究哈!!~~

最后附上demo github链接:
https://github.com/913453448/AndroidUnit

展开阅读全文

没有更多推荐了,返回首页