我对测试的粗浅理解
任何软件的开发过程中,测试绝对是不可或缺的一环,其重要程度几乎和开发不相上下。
然鹅!
在我多年的开发经验中(容我装个B(。・∀・)ノ),遇到的包含单元测试的项目或者愿意主动写测试的程序员却少之又少。
当然我们有一大堆不写测试的理由,比如项目时间太紧张或者项目太简单,测试没必要,再或者我是大神,代码根本不会出现bug。
还有更根本的原因就是大多数开发者在学习过程中,注重的更多是开发相关的知识掌握和技能水平的提高,很少有人注重提高自己的测试水平,大多数人都是了解一下就跳过了(包括我自己,捂脸.jpg)。由于各方面的不重视,现在好多新手对单元测试一脸懵逼。我也亲眼目睹过好多项目在开发伊始,程序员就先删掉了单元测试相关的依赖和文件目录…
其实就算不是为了提早发现代码中隐藏的bug,写单元测试也能给我们的开发过程带来很大的便利。
举一个简单的例子:如果没有单元测试,那么我们如何验证项目中一段代码的正确性,它的输出是不是符合我们的预期?大概你会说我跑一遍项目不就可以了么?事实上你也只能这么干。但是有了单元测试就不一样了,我们不必为了验证一段代码去重新build整个项目,尤其是一些纯Java的,与整个Android SDK无关的代码。这样会大大的提高我我们的开发效率。要知道Android的编译速度,那简直是…男默女泪(也可能是我电脑太渣)
扯了这么多,我就是想说,学会写单元测试对开发者来说很重要,不光是对Android开发者,对任何开发者都是,它是任何一个高逼格的程序员所应当具备的基本素质之一。
再次然鹅!
网上的单元测试教程都特别深奥或者千篇一律,像我这种小白看完之后也是摸不着头脑,也不知道该如何下手,所以我就写了这篇**简单的**Android单元测试教程,也算是抛砖引玉。
官方简介
现阶段,Android Studio对单元测试的支持已经相当人性化了,开发者只需动动鼠标就能构建出相关的测试类,甚至大部分测试代码。而我们只要加上需要测试的部分就可以了。
先贴一段Google爸爸对单元测试的简单介绍:
Getting Started with Testing
Android tests are based on JUnit, and you can run them either as local unit tests on the JVM or as instrumented tests on an Android device.
原文戳这里(需科学上网):https://developer.android.com/training/testing/start/index.html?hl=zh-cn#test-types
测试前的准备
首先要在 moudle-name下的build.gradle文件里面引入相关的依赖:
(只贴出了和测试相关的)
其中Espresso是Google官方提供并推荐使用的测试库。
apply plugin: 'com.android.application'
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
}
然后在你项目的src文件夹下新建测试相关的目录
这几个目录的名字基本上是固定的,一个是androidTest,一个是test;顾名思义,一个是用来进行Android API相关的测试,一个用作Java本地测试。这两个目录下面又各有一个java目录,再下面有以项目的ApplicationId命名的目录,这个名字能不能改我倒是没有研究过,不过最好还是别改了吧。然后再往里面就是你的测试类了。
如果是新建项目自动生成的测试相关目录,这些测试类包括里面的一部分代码都是生成好了的,但是假若新建项目的时候没有勾选自动生成单元测试的选项或者单元测试后来被删除了的话就需要手动新建这些目录和测试类了。
开始编写测试代码并进行单元测试
开始之前我们先来看下Google官方的描述:
Local unit tests
Located at module-name
/src/test/java/
These tests run on the local JVM and do not have access to functional Android framework APIs.
To get started, see Building Local Unit Tests.
Instrumented tests
Located at module-name
/src/androidTest/java/
These are all tests that must run on an Android hardware device or an Android emulator.
Instrumented tests are built into an APK that runs on the device alongside your app under test. The system runs your test APK and your app under tests in the same process, so your tests can invoke methods and modify fields in the app, and automate user interaction with your app.
For information about how to create instrumented tests, see the following topics:
Building Instrumented Unit Tests: Build complex unit tests with Android dependencies that cannot be satisfied with mock objects.
Automating User Interface Tests: Create tests to verify that the user interface behaves correctly for user interactions within a single app or for interactions across multiple apps.
Testing App Component Integrations: Verify the behavior of components that users do not directly interact with, such as a Service or a Content Provider.
从上面的描述我们可以了解到本地化测试和图形测试的步骤:
本地化单元测试
本地化的意思就是你要测试的代码和Android框架的API没有任何的关系,只需要开启Java虚拟机就能运行起来的测试:
打开test下的ExampleUnitTest文件,默认的代码是这个样子的:
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest
{
@Test
public void addition_isCorrect() throws Exception
{
assertEquals(4, 2 + 2);
}
}
这是Google给我们提供的一个单元测试的例子,就是断言两个参数是否相等。可以把光标放在函数名称上,然后按下Ctrl+Shift+F10来运行这个测试:
可以很快看到下方的控制台窗口打印出了绿条,因为这个过程并不需要Android API的支持,它只是开启了一个Java虚拟机来运行这段函数,并给出结果,所以速度是相当快的,比build整个项目不知要快到哪里去了。同时控制台的打印结果也证明这个函数经过测试是符合我们的的预期的,因为2+2显然等于4。
相反的,如果测试没有成功,就会打印红条,这时候就需要查找失败的原因。
仿照Google给出的例子我们也可以在这里测试自己的函数:
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest//类名可以随便取,不过最好是有实际意义的名称
{
@Test//所有的测试都必须加上@Test注解(非常重要!!),这样测试框架才能识别你的代码,并把它当作单元测试进行处理
public void addition_isCorrect() throws Exception
{
assertEquals(4, 2 + 2);
}
//测试无参无返回值的函数
@Test
public void printText()
{
System.out.println("Hello test!");
}
//测试带参数和返回值的函数
@Test
public void printTextWithArgs()
{
System.out.println(text("Hello test with args!"));
}
//被测试的函数
private String text(String text)
{
return text;
}
@Test
public void complexFun()
{
List<String> list = calculateLevel(111);
System.out.print(list);
}
//复杂函数的测试
private List<String> calculateLevel(int level)
{
List<String> list = new ArrayList<>();
int sunNum = level / (5 * 5);
int moonNum = (level - (5 * 5) * sunNum) / 5;
int starNum = level - sunNum * (5 * 5) - moonNum * 5;
//根据不同图标的个数添加不同的元素
for (int i = 0; i < sunNum; i++)
{
list.add("日");
}
for (int i = 0; i < moonNum; i++)
{
list.add("月");
}
for (int i = 0; i < starNum; i++)
{
list.add("星");
}
return list;
}
我们看一下复杂函数的测试结果:
需要注意的点:
- 在test目录下的单元测试,只能测试和Android API无关的函数,例如你要在这里打印log,通常来说就使用Java的System.out.print()函数打印,如果你用Android的Log类去打印,肯定会报错的。
- 所有的测试函数都必须加上@Test注解,只有加了注解,该函数才能被测试框架识别并处理,还有就是带@Test注解的函数一般都是public并且无参无返回的函数,如果你要测试带有参数和返回值的函数,就需要写一个不带@Test注解的函数然后在测试函数里面调用你的被测试函数。
- 运行单个测试函数需要把光标放在测试函数的函数名称上,按Ctrl+shift+F10或者右键选择运行。如果要运行该测试类中的所有测试函数,就把光标放在除了函数名称的任意位置或者直接在导航栏里选中该测试类即可,运行方法和上面单个函数的方法一样。
图形化测试
图形化测试就是要测试的代码必须运行在搭载Android系统的硬件设备或者虚拟机上。
我们先来看下Google给的例子:
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest
{
@Test
public void useAppContext() throws Exception
{
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.mewlxy.qqlevelbar", appContext.getPackageName());
}
}
从这个例子中我们可以看出在Android单元测试中,Google给出了一系列API方便开发者进行Android设备相关的单元测试,例如可以利用
Context appContext = InstrumentationRegistry.getTargetContext();
获取Context变量,有了它,我们就能做好多和Android API相关的测试了。
@Test//Android log打印
public void logTest()
{
Log.d("android", "Hello, android test!");
}
相对于本地测试,UI相关测试的API要稍微复杂一点,我平时用的比较多的也是本地测试。想深入了解UI测试的小伙伴可以戳这里(需科学上网):
https://developer.android.com/topic/libraries/testing-support-library/index.html?hl=zh-cn#espresso-matching
总结
本篇文章的主要目的就是想让大家了解下Android的单元测试,并学会编写单元测试。在今后的开发过程中利用单元测试来提高自己的开发效率。当然单元测试这块的内容也比较多,我也只是了解了一点皮毛,今后还要继续学习。
源码
https://github.com/lxygithub/QQLevelBar