Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中的單元測試(你有用過嗎?O(∩_∩)O~)

Android中的單元測試(你有用過嗎?O(∩_∩)O~)

編輯:關於Android編程

前言: 周末真的是除了睡覺還是睡覺啊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 getDistirctsByPcode(String cityCode) throws Exception;

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

    /**
     * 獲取所有的省份
     * @return
     * @throws Exception
     */
    List 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 getDistirctsByPcode(String cityCode) {
        List cities = new ArrayList();
        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 getCitiesByPcode(String pCode) throws Exception {
        List cities = new ArrayList();
        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 getAllProvince() {
        List provinces = new ArrayList();
        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 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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved