单元测试——使用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); } }
相关文章推荐
- 使用MOCK对象进行单元测试
- 单元测试中NUnit和DotNetMock的使用调研报告
- java.lang.VerifyError 在使用PowerMock EasyMock进行单元测试
- 使用 Spring + Mockito+PowerMock +spring-test-dbunit+hsqldb 进行单元测试可实现100%覆盖率
- 使用Mockito进行单元测试【1】——mock and verify[转]
- 使用MOCK对象进行单元测试
- 有效使用Mock编写java单元测试
- 使用Powermock进行单元测试,以及常见问题的处理
- 有效使用Mock编写java单元测试
- 使用MRUnit,Mockito和PowerMock进行Hadoop MapReduce作业的单元测试
- 使用 MOCK 对象进行单元测试
- 使用MOCK对象进行单元测试
- 使用Mock对象进行单元测试
- 使用 PowerMock 以及 Mockito 实现单元测试
- JAVA使用 MOCK 对象进行单元测试的实例讲解
- 使用MOCK对象进行单元测试
- 使用 MOCK 对象进行单元测试的实例讲解
- 使用MOCK对象进行单元测试
- Spring MVC的单元测试和集成测试(不使用mock)
- 使用MRUnit,Mockito和PowerMock进行Hadoop MapReduce作业的单元测试