Android测试:从零开始2——local单元测试
上一篇分析了android项目的测试分类,这一篇讲local单元测试。
测试前需要配置测试环境,新建项目后,目录下会出现app/src/test/java/文件夹,这个文件夹是用于存放local单元测试代码的(就是与androidAPI无关的测试)。
在build.gradle配置文件中增加测试依赖:
dependencies {
// Required -- JUnit 4 framework
testCompile 'junit:junit:4.12'
// Optional -- Mockito framework
testCompile 'org.mockito:mockito-core:1.10.19'
}
使用JUnit4测试需要了解下几个基本的注解:
@Before:
使用这个注解可以做一些在测试之间的准备工作。在每一个测试方法之前都会调用这块代码。可以定义多个@Before块,但是多个的调用顺序是不确定的。
@After:
这个注解定义一些测试结束执行的清除工作。在每个测试方法结束后都会调用这块代码。可以定义多个@After块,但是多个的调用顺序是不确定的。
@Test:
这个注解标记测试的方法,一个测试类当中可以定义多个测试方法,每个测试方法都需要使用@Test标记。
@Rule:
使你可以以复用的方式灵活的增加和重新定义每个测试方法的行为。在Android测试中会使用Android Testing Support Library提供的rules,如:ActivityTestRule 或者 ServiceTesteRule。
@BeforeClass:
使用这个注解为测试类设置一个静态方法,一个测试类里面只执行一次。这个方法通常注解一些耗时比较长的操作,如连接数据库。
@AfterClass:
使用这个注解设置一个测试类运行结束后执行的方法。这个方法通常用来释放@BeforeClass里面分配的资源。
@Test(timeout=)
这个注解用来设置一个超时参数,如果测试在设置的时间内没有执行完,就会抛出异常。
另外有时候需要模拟Android的代码,可以使用@Mock注解来模拟。
以下是官网上的一个简答实例:
import org.junit.Test;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; public class EmailValidatorTest { @Test
public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
}
...
}
使用junit.Assert方法来比较运行结果和期望值。为了使测试更直观,可以使用Hamcrest(is()和equal())来匹配结果和期望值。
接下来以googlesamples的Android Architecture Blueprints为例,分析下单元测试在项目中的使用。
我取的是todo-mvc-dagger分支的代码,项目有两个Variant(mock和prod)。mock的项目结构如下:
prod的项目结构如下
项目是MVP架构,将业务逻辑从Activity中独立出来,放在presenter里面,对presenter非常适合local单元测试,执行相对比较快。
看下mock下的test里面的TaskDetailPresenterTest测试:
package com.example.android.architecture.blueprints.todoapp.taskdetail; import com.example.android.architecture.blueprints.todoapp.data.Task;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository; import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; /**
* Unit tests for the implementation of {@link TaskDetailPresenter}
*/
public class TaskDetailPresenterTest { public static final String TITLE_TEST = "title"; public static final String DESCRIPTION_TEST = "description"; public static final String INVALID_TASK_ID = ""; public static final Task ACTIVE_TASK = new Task(TITLE_TEST, DESCRIPTION_TEST); public static final Task COMPLETED_TASK = new Task(TITLE_TEST, DESCRIPTION_TEST, true); @Mock
private TasksRepository mTasksRepository; @Mock
private TaskDetailContract.View mTaskDetailView; /**
* {@link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
* perform further actions or assertions on them.
*/
@Captor
private ArgumentCaptor<TasksDataSource.GetTaskCallback> mGetTaskCallbackCaptor; private TaskDetailPresenter mTaskDetailPresenter; @Before
public void setup() {
// Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this); // The presenter won't update the view unless it's active.
when(mTaskDetailView.isActive()).thenReturn(true);
} @Test
public void getActiveTaskFromRepositoryAndLoadIntoView() {
// When tasks presenter is asked to open a task
mTaskDetailPresenter = new TaskDetailPresenter(
ACTIVE_TASK.getId(), mTasksRepository, mTaskDetailView);
mTaskDetailPresenter.start(); // Then task is loaded from model, callback is captured and progress indicator is shown
verify(mTasksRepository).getTask(eq(ACTIVE_TASK.getId()), mGetTaskCallbackCaptor.capture());
InOrder inOrder = inOrder(mTaskDetailView);
inOrder.verify(mTaskDetailView).setLoadingIndicator(true); // When task is finally loaded
mGetTaskCallbackCaptor.getValue().onTaskLoaded(ACTIVE_TASK); // Trigger callback // Then progress indicator is hidden and title, description and completion status are shown
// in UI
inOrder.verify(mTaskDetailView).setLoadingIndicator(false);
verify(mTaskDetailView).showTitle(TITLE_TEST);
verify(mTaskDetailView).showDescription(DESCRIPTION_TEST);
verify(mTaskDetailView).showCompletionStatus(false);
} }
这对任务详细页进行了完整的测试(我只粘贴了一个@Test方法)。在@Before方法里面调用
MockitoAnnotations.initMocks(this);
来模拟@Mock标记的TaskRepository和TaskDetailContract.View。
在@Test测试方法里面,实例化presenter,并且调用start方法,使用mockito的verify方法来校验是否按预期的调用了TaskRepository和TaskDetailContract.View里面的方法。
googlesamples里面有详尽完整的测试,路漫漫,其修远兮,囧囧囧囧。
Android测试:从零开始2——local单元测试的更多相关文章
- android测试的相关概念以及单元测试
1.测试的相关概念 1.根据是否知道源代码分类: 黑盒测试: a - b - c 边值测试 白盒测试: 根据源代码写测试方法 或者 测试用例; 2.根据测试的粒度分类: 方法测试:写完一个方 ...
- Android测试:Building Local Unit Tests
原文:https://developer.android.com/training/testing/unit-testing/local-unit-tests.html 如果你的单元测试没有依赖或者只 ...
- Android测试:从零开始1——简介
参考文档:https://developer.android.com/training/testing/start/index.html 测试分类 使用android studio进行测试,首先需要先 ...
- Android测试(四):Instrumented 单元测试
原文:https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html Instrume ...
- 【Android测试】【第十五节】Instrumentation——官方译文
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5482207.html 前言 前面介绍了不少Android ...
- 【Android测试】【第十一节】Uiautomator——简介
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4872244.html 前言 在App的测试中,除了单元测试 ...
- Android studio下gradle Robolectric单元测试配置
android studio下gradle Robolectric单元测试配置 1.Robolectric Robolectric是一个基于junit之上的单元测试框架.它并不依赖于Android提供 ...
- 【Android测试】【第九节】MonkeyRunner—— 初识
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4836815.html 不得不说两句,过了这么久才再次更新博 ...
- 【Android测试】【第一节】ADB——初识和用法
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4630046.html 写在前面的话 感觉自己进入Andr ...
随机推荐
- Linux命令-权限管理命令:chgrp
groupadd shuaige 创建一个用户组名字叫shuaige ls -l /home/wangyunpeng/abcd 查看abcd文件的权限 chgrp shuaige /home/wang ...
- express@4.0.*
$ sudo npm install -g express the installation goes fine. But when I try to create a project with ex ...
- Timer Schedule参数说明
Timer是一种定时器工具,用来在一个后台线程计划执行指定任务.它可以计划执行一个任务一次或反复多次. TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务. schedule的意 ...
- 【剑指Offer面试题】 九度OJ1510:替换空格
c/c++ 中的字符串以"\0"作为结尾符.这样每一个字符串都有一个额外字符的开销. 以下代码将造成内存越界. char str[10]; strcpy(str, "01 ...
- ffmpeg command
1. 列出当前系统的设备列表 ffmpeg -list_devices true -f dshow -i dummy 2. 列出设备Integrated Camera的信息 ffmpeg -list_ ...
- CYQ学习主要摘要
这个个人搞的框架不知道到底怎么样,做个项目测试下看看,本来想用EF的,大多数人说性能低,所以先放弃,试试这个,一些主要地方我做个摘录 5:多表怎么搞定? A:视图方式,将多表查询放到数据库里成视图,情 ...
- ResourceBundle读取utf-8格式properties 中文乱码
解决方法: ResourceBundle prop = ResourceBundle.getBundle("app");String defaultTunnelName = new ...
- tftp server setup
今天开始调试ARM的板子,要通过tftp下载到板子上,所以又要配置tftp服务器,真的烦死了… (本人酷爱装系统,所以经常都要搞配置) 因为之前已经在Ubuntu下搭建过很多次tftp服务器了,但是一 ...
- Differential Geometry之第八章常Gauss曲率曲面
第八章.常Gauss曲率曲面 1.常正Gauss曲率曲面 2.常负Gauss曲率曲面与Sine-Gordon方程 3.Hilbert定理 4.Backlund变换 4.1.线汇与焦曲面 4.2.Bac ...
- 微服务vs传统开发
使用微服务有一段时间了,这种开发模式和传统的开发模式对比,有很大的不同. 分工不同,以前我们可能是一个一个模块,现在可能是一人一个系统. 架构不同,服务的拆分是一个技术含量很高的问题,拆分是否合理对以 ...