前言 我开发经验比较少,这公司也不算太靠谱,由于经验尚浅,很多代码有的时候也有不少毛毛糙糙的地方,没测试,那就自己撸起袖管上吧。本文小记一下我看别人的文所得和一些翻译。
创建有效的单元测试 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文档!
本地测试 首先引入测试框架:
1 2 3 4 dependencies { testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' }
JUnit是Java里最受欢迎也是应用最广泛的测试框架,而mockito是模拟测试框架。这二者有什么关系呢?我们写代码的时,各种类之间充满了依赖关系。当你测试一个类的时候,可能并不想测试他所依赖的类是否正常,因为你默认它是正常好用的。那么这个时候你就可以用Mockito框架,创建一个模拟对象,JUnit 4比之前好用了不少,只用添加各种注解就能完成简单的测试,看到这你一定很感兴趣了,那么让我们来看一段简单的代码: 首先在新建项目,在项目里代码文件夹下新建一个Calculator类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /** * 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测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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类改为正确的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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测试框架来创建一个模拟对象。下面的小例子只是为了说明一下创建模拟对象,就不去耗费脑细胞想应用场景了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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下加入了这句话:
1 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
xml布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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() + "!" ); } }
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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;@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))); } }
看一下运行的效果图:
最后放下几个测试框架的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
这几个例子不是很详细,因为我也是在摸索。。。