Android测试

前言

我开发经验比较少,这公司也不算太靠谱,由于经验尚浅,很多代码有的时候也有不少毛毛糙糙的地方,没测试,那就自己撸起袖管上吧。本文小记一下我看别人的文所得和一些翻译。

创建有效的单元测试

Building Effective Unit Tests 这个是原文地址,想看原文的可以自己看一下,下面是我自己的翻译= =,比较渣。

单元测试是在你的应用中基本的测试策略。通过创建和运行单元测试检验你的代码,你可以很容易校验个别单元的逻辑是否正确。当你重构代码时,运行单元测试能帮助你快速的修复软件和复原。

单元测试通常是反复测试尽可能小的代码单元(可以是一个方法,类或者组件)。你应该在你的app中的特定代码逻辑需要校验的时候创建单元测试。举个例子来说:如果你正在对一个类进行单元测试,你的测试可能会检查那个类是否处于一个正常的状态。通常被测试的代码单元是孤立的。你的测试仅仅影响和检测那个单元的变化。 mocking framework可以被用来使你的单元从他的依赖上隔离。

注意:单元测试并不适合测试复杂的UI相互作用的事件。取而代之的是,你应该使用UI 测试框架,在 Automating UI Tests 中有描述。

为了测试Android apps,你通常会创建这些种类的自动化单元测试:

  • Local tests(本地测试):单元测试只在你本地的机器上运行,这些测试在最短时间内被编译在Java虚拟机上运行。通过这个途径运行单元测试不必依赖Android框架或者和使用摸你对象填充有依赖关系。
  • Instrumented tests(仪器?测试):在Android设备或模拟器上运行单元测试。这些测试可使用仪器信息,比如为了app测试使用Context上下文。通过这个途径运行单元测试拥有Android的依赖不可以轻易的使用模拟对象。

** Building Local Unit Tests **
学习如何创建运行在你本地机器上的单元测试

** Building Instrumented Unit Tests **
学习如何创建运行在Android设备或者模拟器上的单元测试

小例子

我在这写的两个小例子都是非常简单的,无法作为应用在项目中的参考,如果有你有这个需求,需要你自己去看这几个测试框架的api文档!

本地测试

首先引入测试框架:

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.10.19'
}

JUnit是Java里最受欢迎也是应用最广泛的测试框架,而mockito是模拟测试框架。这二者有什么关系呢?我们写代码的时,各种类之间充满了依赖关系。当你测试一个类的时候,可能并不想测试他所依赖的类是否正常,因为你默认它是正常好用的。那么这个时候你就可以用Mockito框架,创建一个模拟对象,JUnit 4比之前好用了不少,只用添加各种注解就能完成简单的测试,看到这你一定很感兴趣了,那么让我们来看一段简单的代码:
首先在新建项目,在项目里代码文件夹下新建一个Calculator类:

/**
 * Created by xiasuhuei321 on 2017/2/4.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */

public class Calculator {

    public double sum(double a, double b){
        return 0;
    }

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

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

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

可以看到我都是返回0,故意的,看看等会测试能不能测出来,接着在test的代码文件夹下新建CalculatorTest测试类:

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

import static org.junit.Assert.*;

/**
 * Created by xiasuhuei321 on 2017/2/4.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */
public class CalculatorTest {

    private Calculator mCalculator;

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

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

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

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

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


}

上面@Before意思是在 @Test注解的方法之前执行这个方法,可以用来初始化一些类。@Test自然就是测试方法了。

接着右键点击测试类,选择Run:


运行

]
结果显而易见没通过测试:

没通过测试

整个过程都没有将程序运行到Android设备上,和上面讲的一样,这就是在本地的JVM虚拟机上跑的,很方便。现在我们将Calculator类改为正确的逻辑:

/**
 * Created by xiasuhuei321 on 2017/2/4.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */

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;
    }
}

再次运行刚写好的单元测试,看看结果:

通过

这次通过了,可以发现在修改源代码之后,可以通过这个单元测试的代码来校验修改后的代码逻辑。这对于重构代码来说很有帮助。

上面只是一个简单的例子,在实际代码中,可能我们通过构造方法创建一个对象的时候,还需要依赖另外一个对象,但是如果依赖的对象对我这个测试并没有什么影响,那么就可以用Mockito测试框架来创建一个模拟对象。下面的小例子只是为了说明一下创建模拟对象,就不去耗费脑细胞想应用场景了:

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

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * Created by xiasuhuei321 on 2017/2/4.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */

@RunWith(MockitoJUnitRunner.class)
public class MockTest {
    @Mock
    Context mContext;

    @Test
    public void testAppName() {
        when(mContext.getString(R.string.app_name))
                .thenReturn("JpushDemo");
        assertEquals("名字不同!", "JpushDemo", mContext.getString(R.string.app_name));
    }
}

可以看到我类里有一个@Mock注解的Context类引用,在测试方法里我通过这个Context拿到了一个值,然后对比这两个值是否一致。这里就不贴通过测试的图了,各位感兴趣可以自己去看api文档。

在物理设备上的测试

原话是Building Instrumented Unit Tests,上面介绍的是在本地JVM虚拟机上运行的检测单元逻辑的测试,这里的可以用来检测UI之类的逻辑,上一个网上看到的例子。

首先是配置环境,我没怎么配置,可能是高版本的as已经自己加入了这个测试框架了,我只在app下的build.gradle里的android下加入了这句话:

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

xml布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.xiasuhuei321.jpushdemo.MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView"
        android:hint="Enter your name here" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/editText"
        android:onClick="sayHello"
        android:text="Say hello!" />
</RelativeLayout>

MainActivity:

package com.xiasuhuei321.jpushdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    public void sayHello(View v) {
        TextView textView = (TextView) findViewById(R.id.textView);
        EditText editText = (EditText) findViewById(R.id.editText);
        textView.setText("Hello," + editText.getText().toString() + "!");
    }
}

测试代码:

package com.xiasuhuei321.jpushdemo;

import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

/**
 * Created by xiasuhuei321 on 2017/2/4.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityInstrumentationTest {
    private static final String STRING_TO_BE_TYPED = "Peter";

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void sayHello() {
        onView(withId(R.id.editText))
                .perform(typeText(STRING_TO_BE_TYPED),
                        closeSoftKeyboard());

        onView(withText("Say hello!")).perform(click());

        String expectedText = "Hello," + STRING_TO_BE_TYPED + "!";
        onView(withId(R.id.textView))
                .check(matches(withText(expectedText)));
    }
}

看一下运行的效果图:

6.gif

最后放下几个测试框架的api文档地址:
JUnit:http://junit.sourceforge.net/javadoc/

Mockito:http://static.javadoc.io/org.mockito/mockito-core/2.7.1/overview-summary.html

Espresso(UI自动测试框架):https://google.github.io/android-testing-support-library/docs/espresso/index.html

这几个例子不是很详细,因为我也是在摸索。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容