您的位置:首页 > 其它

单元测试——使用Mock

2016-09-08 12:52 225 查看

一、Mock简介

背景:小明和小刚要为app制作一个登陆功能,小明负责网络交互获取服务器的认证数据,小刚负责将获取到的数据写入到数据库中,然后能够使用户自动登陆。小刚的开发的速度比较快,已经完成了数据库互这一块,但是小明的网络交互还没有开发完成。那么小刚怎么测试自己的数据库是否完成呢?小刚就要自己创建一个类,模拟登陆,然后进行测试。
首先创建User类
public class User {
private String id;
private String pwd;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}

@Override
public String toString() {
return "User [id=" + id + ", pwd=" + pwd + "]";
}
}
然后创建数据库存储类UserDao
public class UserDao {

public void saveUserInfo(User user){
//存储数据
System.out.println(user);
}
}
之后创建自己的测试登陆类Login (这就是一个简单的Mock)
public interface ILogin {
public User login();
}

public  User login(){
User user = new User();
ser.setId("1");
user.setPwd("123");
return user;
}
最近创建测试类进行测试SaveTest
public class SaveTest extends TestCase{
private UserDao mUserDao;
private ILogin mLogin;
@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
mUserDao = new UserDao();
mLogin = new LoginImpl();
}

public void testSave(){
mUserDao.saveUserInfo(mLogin.login());
}

@Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
super.tearDown();
mUserDao = null;
mLogin = null;
}
}
根据上面的例子我们可以得出:Mock代表的是模拟某个类(Login类)的行为。这个类是测试你的模块必不可少的类。(必须得有Login才能调用saveUser()存储User)。
但是如果测试模块需要多个未完成的外部类,那么岂不是需要些很多个Mock。为了简化这个过程,就出现了Mockito库。

二、Mockito的使用

①Mockito两个重要的概念
Mock:对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。
Stub:完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。(调用盖房只为获取方法的返回值,且该返回值是由自己设定。形式就是调用了方法,没有调用方法的内部实现,然后返回自己设定的返回值)

②、使用
Mock的作用
1、测试对象的行为次数(从未、至少、至多、具体次数)
//设置为静态import
import static org.mockito.Mockito.*;

public class TestMock extends TestCase {

public void testList(){
//知识点①
List mockList = mock(List.class);
//知识点③
mockList.add("1");
//知识点②
verify(mockList,times(2)).add("1");
}

}
①、使用类的Class类创建测试对象,该类可以是接口、或者具体类。因为并非真正的调用该类的真实方法。
②、verify(Object obj,VerficationMode mode):计算该类的方法被调用的次数。
Object obj:表示被监控的类。
VerficationMode mode:表示次数。 一般使用times(int times)来设置。
默认VerficationMode mode 为 times(1);
.xxx():XXX表示类中具体的方法。
③、发现方法的调用必须在verify()方法前。因为verify()方法是计算在其被调用前方法的使用次数。

常用的verify()方法:
其实修改的只是判定方法被调用的次数。
verify(mockList,times(2)).add("1");

//never():从未被调用过
verify(mockList,never()).add("1");
//asLeast():至少调用的次数
verify(mockList,atLeast(1)).add("1");
//atMost():至多调用的次数
verify(mockList,atMost(5)).add("1");
//times():具体调用的次数
verify(mockList,times(3)).add("1");


Stub的使用
1、为对象stub(打桩:就是自己设定返回值)
public class TestMock extends TestCase {
public void testList(){
//知识点①
LinkedList list = mock(LinkedList.class);
//知识点②
when(list.get(0)).thenReturn(5);
//知识点③
int data =(int)list.get(0);
System.out.println(data);//输出:5.
}
}
①、能够直接获取实现类的Mock对象,不光能获取接口类的Mock对象
②、when(T methodCall).thenReturn(T data):表示监控该类的这个方法,当该方法调用的时候,返回thenReturn(T data)中的data数据。 根据方法可知,该类的方法必须要有返回值才能用when(),如果方法没有返回值怎么办,稍后会讲到
③、验证方法的使用是否正确。

常用打桩的方法:
①、thenReturn:返回具体的数值
②、thenThorrow:返回异常。

通过这个方法我们可以改写

2、利用参数匹配器,来设定输入参数的类型。
参数匹配器,表示该方法能够接收某一类参数
参数匹配器的种类:anyXXX(),XXX代表基础引用类型。例:anyInt()、anyString()、等
例:
public class TestMock extends TestCase {
public void testList(){
//知识点①
LinkedList<String> list = mock(LinkedList.class);
//知识点②
when(list.get(anyInt())).thenReturn("asd");
//知识点③
String data =(String)list.get(1);
String data2 = (String)list.get(2);
System.out.println(data+"  "+data2);
}
}

还可以这样使用:
verify(mock).someMethod(anyInt(),anyString);

但是不能
verify(mock).someMethod(anyInt(),"asd");//如果使用参数匹配器,那么全部的输入参数都得是参数匹配器,而不能是一个具体的参数。

3、为连续的调用做测试桩
public class TestMock extends TestCase {
public void testList(){
LinkedList<String> list = mock(LinkedList.class);
//连续调用thenXxx()方法
when(list.get(anyInt())).thenReturn("asd")
.thenThrow(new RuntimeException());
String data =(String)list.get(1);
System.out.println(data);//返回asd
String data2 = (String)list.get(2);//报:RuntimeException()的错

}
}


4、制作回调测试桩
public void testList(){
List<String> list = mock(List.class);
//知识点①
when(list.get(anyInt())).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
// TODO Auto-generated method stub
//获取数据的参数
Object [] objs = invocation.getArguments();
//获取mock对象本身
Object mock = invocation.getMock();
return "返回的数据"+objs[0];
}
});
String str = list.get(12);
System.out.println(str);
}
知识点①、通过调用thenAnswer(),然后调用方法后,会触发Answer的回调,然后通过invocation获取该方法的输入参数。然后进行判定。感觉相当于在Answer对象类,写方法的实现。

5、简化Mock的创建过程
public class TestMock extends TestCase {
@Mock
private User mUser;
@Mock
private SaveTest mSaveTest;
@Mock
private ILogin mLogin;

@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
//初始化注解的类,
MockitoAnnotations.initMocks(this);
//然后就可以了直接使用了,不需要
ILogin login = mock(ILogin.class);
}

@Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
super.tearDown();
}

}


6、doReturn()、doThrow()、doAnswer()、doNothing()方法的使用
原理:当执行了某个类的方法,之后就执行doXxx()方法。
使用情形:
1、测试void方法 (因为when中的方法必须得有返回值,才能使用thenXxx()方法)
注: doReturn()方法,测试的必须得是有返回值的方法,否则会报错
2、在受监控的对象上测试(结合"监控一起讲解")
对情形1的举例
<span style="font-size:18px;">//使用情形:测试void方法。
public class TestMock extends TestCase {
private User user;
public void testMock(){
User user = mock(User.class);
doThrow(new RuntimeException()).when(user).setId();
user.getId();
}
}//结果抛出了异常</span>

我们在测试下,调用doReturn(),调用没有返回值的方法(void)
private User user;
public void testMock(){
User user = mock(User.class);
doReturn("asd").when(user).setId("zxc");
}//报错了,doReturn不能使用void的方


7、监控(spy)真实对象
spy:能够调用真实对象的方法,而不是像打桩(stub)只调用方法,不调用的内部实现。
举例:
private User user;
public void testMock(){
User user = new User();
User spy = spy(user);
spy.getId();

//相当于
User user = new User();
user.getId();
}
作用:变为spy之后,就相当变成了一个Mock,也就是能够调用Mock的所有实现(之前说到的verfity判断、做测试桩)
例:
public void testMock(){
List list = new LinkedList();
List spy = spy(list);
//1、设置测试桩   错误无法使用。
when(spy.get(0)).thenReturn("asd");

//2、利用doXxx()建立测试桩
doReturn("asd").when(list).get(0);

//3、测试次数
verify(list).get(0);
}


但是发现直接使用1、设置测试桩的功能,会报错。因为spy.get(0)调用了真实对象list的方法,但是此时list中是没有值的,所以会调用IndexOutOfBoundsException的异常。
那么我们将1删除,测试2,发现2能够好好的返回asd。说明使用doXxx()是不会调用真实对象的方法。这就印证了doXxx()的使用情形2。(spy类中,不推荐使用thenXxx(),推荐使用doXxx())。

三、综合实例
我们继续第一个例子,小刚需要完成1、将登陆的数据装入数据库。2、假设登陆之后,用户需要搜索一篇文章,小刚要先从数据库中提取,如果数据库没有这篇文章的话,再向网络获取
1、首先之前我们已经建立了未使用Mockito工具的测试,现在我们修改一下,使用Mockito来完成该项目。
首先回顾一下之前我们建立的类
User:JavaBean,将传过来的数据转化为对象
UserDao:存储类,用来将数据存入数据库
ILogin:用来登陆的接口
LoginImp:登陆的实现类,将数据解析成User对象
SaveTest:测试类,测试是否数据库存储实现没问题。
那么由于我们拥有的Mockito,就表示我们没必要具体实现Login这个类的login方法,只要确定返回值是User就可以了。
所以可以删除LoginImp,然后修改SaveTest的实现:
import static org.mockito.Mockito.*;

public class SaveTest extends TestCase{
private UserDao mUserDao;
private ILogin mLogin;
@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
//初始化对象
mUserDao = new UserDao();
mLogin = mock(ILogin .class);
}

public void testSave(){
//定义返回的User
User user = new User();
user.setId("number");
user.setPwd("123");
//设置返回值
when(mLogin.login()).thenReturn(user);
//实现数据库的存储
mUserDao.saveUserInfo(mLogin.login());
}

@Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
super.tearDown();
mUserDao = null;
mLogin = null;
}
}


2、那么现在我们需要制作提取数据的功能了。
测试的目的:测试获取Article的模块是否能够使用,并且不出bug
分析:
1、在ArticleDao类中,添加提取数据库数据的功能。(自行创建Article类,与User的创建方式相同)
2、自行创建获取Article的网络的接口(与创建ILogin一样)
3、创建ArticleController类:用来处理获取数据的逻辑(判断是否数据库中含有值,如果没有则从网络获取)
4、进行测试。
第一步:
public class ArticleDao {

public void saveArticleInfo(Article article){
//存储数据
System.out.println(article);
}

public User getArticleFromDb(int id){
//为了简便,仿造数据
if (id < 100){
return null;
}
else {
Article article= new Article();
article.setId(id+"");
article.setContent("zxcvcv");
return article;
}

}
}
第二步:
/**
* 功能
* 获取Article的数据:是从网络还是从本地数据库
* 1、首先确定是否从本地获取数据
* 2、当本地没数据的时候从网络中获取数据
*/
public class ArticleController {
private ArticleDao mArticleDao;
private IGetArticle mGetArticle;

public UserController(IGetArticle getArticle){
mGetArticle= getArticle;
}

public User getArticle(int id){
Article article = null;
if(mArticleDao != null){
article = mArticleDao.getUserFromDb(id);
}
//从网络获取数据
if (article== null){
article = mGetArticle.getArticle();

}
return article;
}

public void setUserDao(ArticleDao articleDao){
mArticleDao = articleDao;
}
}
第三步:
确定Mock类:IGetArticle类

确定测试类:ArticleController类。能否从数据库中获取到值,如果获取不到值,能否从网络中获取到值....
public class GetUserTest extends TestCase{
private IGetArticle mGetArticle;
private ArticleDao mArticleDao;
private ArticleController mController;

@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
//初始化mock类
mGetArticle = mock(IGetArticle.class);
//初始化测试类
mController = new ArticleController(mLogin);
mArticleDao = new ArticleDao();
}

public void testGetArticle(){
//第一步:测试当从UserDao中获取到值的情况,那么结果一定不为null
mController.setArticleDao(mArticleDao);
when(mGetArticle.getArticle()).thenReturn(null);
Article article = mController.getArticle(120);
System.out.println(article);

//第二步:测试UserDao返回null的时候,直接从网络中赋值
Article myArticle = new Article();
myArticle.setId("123");
myArticle.setPwd("zxc");
when(mGetArticle.getArticle()).thenReturn(myArticle);

Article newArticle = mController.getArticle(1);
System.out.println(newArticle);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: