【译】如何在 Mockito 中进行单元测试

阅读 923
收藏 8
2016-07-01
原文链接:www.sdk.cn

这篇文章解释了如何用Mockito进行测试的方法,从而编写软件测试。

1. 先决条件

这篇指导需要你理解JUnit框架下的单元测试。

如果你对JUnit不熟悉,请参看以下链接:: http://www.vogella.com/tutorials/JUnit/article.html.

2. 使用模拟对象进行测试

2.1. 单元测试的目标和挑战

单元测试应该对类别进行隔离测试。在可能的情况下,应该要消除对其他类或系统的副作用。要想消除这些副作用,你必须替换掉其他类的依赖。你可以通过替换真实依赖的方式来完成这个目的。

2.2. 不同测试类别的分类

虚拟对象会被传送,但是从不被使用。例如,它的方法从来不会被调用。这样的对象可以被用来填充方法的参数列表。

伪造对象有其实际作用,但是通常都会被简化。例如,他们使用的是内存数据库,而不是真正的数据库。

存根类是对界面或类的部分实现,其目的是在测试过程中使用这个存根类的实例。存根还会记录有关调用的信息。

模拟对象是接口或类的虚拟实现,你需要定义这个接口或对象的特定方法调用的输出。

测试替代可以被送到其他正在测试的对象中。在测试过程中,你的测试可以使类反应正确生效。例如,你可以确认模拟对象的特定方法是否已经被调用。它可以帮你确认在运行测试期间,你只是对类进行测试,并且你的测试不会受到任何副作用的干扰。

模拟对象通常情况下都已经完成了配置,对它们进行配置所需的代码数量更少,因此更加受到编程人员的欢迎。

2.3. 模拟对象的生成

你可以手动创建模拟对象(通过代码),或是使用模拟框架来模拟这些类。模拟框架允许你在执行期创建模拟对象,并且定义它们的行为。

数据源(data provider)就是一个典型的模拟对象的例子。在运行中,它会使用一个实现来连接真实的数据源。但是在测试期间,模拟对象会对数据源进行模拟,并且确保这个测试条件一直保持统一。

这些模拟对象可以提供给正在被测试的类。因此,被测试的类应该避免任何对外部数据的硬依赖。

模拟或模拟框架允许测试使用模拟对象对期待中的反应进行测试。例如,你可以确认模拟对象中只有特定的方法被调用。

2.4. 使用Mockito对对象进行模拟

Mockito是一个备受欢迎的模拟框架,它可以与JUnit一起使用。Mockito允许你创建模拟对象,并且对其进行配置。使用Mockito,在对类进行测试的时候,它可以极大的简化外部依赖。

在使用Mockito进行测试的时候,你通常可以:

• 移除外部依赖,并且在被测试的代码中插入模拟

• 执行被测试的代码

• 确认这些代码被正确执行

图片1.jpg

3. 给项目添加Mockito作为依赖

3.1. 使用Gradle

如果你使用Gradle,你可以在Gradle开发文件中添加以下依赖:

repositories { jcenter() }

dependencies { testCompile "org.mockito:mockito-core:2.0.57-beta" } 

3.2. 使用Maven

Maven用户也可以添加依赖。在search.maven.org这个网站中搜索 g:"org.mockito", a:”mockito-core,来寻找正确的添加方式。

3.3. 使用Eclipse IDE

Eclipse IDE同时支持Gradle和Maven。最新版的Mockito并没有提供“完全下载”选项。因此,在使用Eclipes的时候,你最好在Gradle和Maven中二选其一。

3.4. OSGi或Eclipse插件开发

在Eclipse RCP程序中,依赖通常需要在p2 update页面获取。Orbit repo是获取第三方库很好的资源,这些库可以用在基于Eclipse的程序或插件中。

The Orbit repo可以在这里找到 http://download.eclipse.org/tools/orbit/downloads

图片2.jpg

4. 使用Mockito API

4.1. 静态导入

 如果你为org.mockito.Mockito.*;添加静态导入,你可以直接获取mock()等Mockitos方法。静态导入允许你调用静态成员。

 4.2. 使用Mockito创建并配置模拟对象

 Mockito支持对模拟对象的创建,你可以使用静态mock()方法来实现。

Mockito还支持基于@Mock注解的模拟对象。

如果你使用这个注解,你必须对模拟对象进行初始化。MockitoRule允许这个操作。它会唤起MockitoAnnotations.initMocks(this)这个静态方法来填充注解字段。除此以外,你还可以使用@RunWith(MockitoJUnitRunner.class)这个静态方法。

下面的例子展示了@Mock注解和MockitoRule规则的使用方法:

import static org.mockito.Mockito.*;

public class MockitoTest  {

        @Mock

        MyDatabase databaseMock;

         @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

        @Test

        public void testQuery()  {

                ClassToTest t  = new ClassToTest(databaseMock);

                boolean check = t.query("* from t");

                assertTrue(check);

                verify(databaseMock).query("* from t");

        }

}

4.3. 配置模拟

要想在方法调用中配置那些值被返回,Mockito框架定义了一个Fluent API。

when(… .).thenReturn(… .)方法链可以用来具体说明一个条件,以及这个条件的返回值。如果你说明的条件大于一个,他们会按照规格顺序返回,直到最后返回的值被利用。根据被传送进方法的引数的不同,Mocks还可以返回不同的值。你也可以使用anyString或是anyInt等方法来定义输入值的独立性,一个特定的返回值会被返回。

import static org.mockito.Mockito.*;

import static org.junit.Assert.*;

@Test

public void test1()  {

        //  create mock

        MyClass test = Mockito.mock(MyClass.class);

        // define return value for method getUniqueId()

        when(test.getUniqueId()).thenReturn(43);

 

        // use mock in test....

        assertEquals(test.getUniqueId(), 43);

}

 

 

// Demonstrates the return of multiple values

@Test

public void testMoreThanOneReturnValue()  {

        Iterator i= mock(Iterator.class);

        when(i.next()).thenReturn("Mockito").thenReturn("rocks");

        String result=i.next()+" "+i.next();

        //assert

        assertEquals("Mockito rocks", result);

}

 

// this test demonstrates how to return values based on the input

@Test

public void testReturnValueDependentOnMethodParameter()  {

        Comparable c= mock(Comparable.class);

        when(c.compareTo("Mockito")).thenReturn(1);

        when(c.compareTo("Eclipse")).thenReturn(2);

        //assert

        assertEquals(1,c.compareTo("Mockito"));

}

 

// this test demonstrates how to return values independent of the input value

 

@Test

public void testReturnValueInDependentOnMethodParameter()  {

        Comparable c= mock(Comparable.class);

        when(c.compareTo(anyInt())).thenReturn(-1);

        //assert

        assertEquals(-1 ,c.compareTo(9));

}

 

// return a value based on the type of the provide parameter

 

@Test

public void testReturnValueInDependentOnMethodParameter()  {

        Comparable c= mock(Comparable.class);

        when(c.compareTo(isA(Todo.class))).thenReturn(0);

        //assert

        Todo todo = new Todo(5);

        assertEquals(todo ,c.compareTo(new Todo(1)));

}

 

doReturn(… ).when(… ).methodCall调用链效果类似,但是在使用void方法时更加使用。具体使用方法请参看下面的实例:

 

import static org.mockito.Mockito.*;

import static org.junit.Assert.*;

 

// this test demonstrates how use doThrow

 

@Test(expected=IOException.class)

public void testForIOException() {

        // create an configure mock

        OutputStream mockStream = mock(OutputStream.class);

        doThrow(new IOException()).when(mockStream).close();

 

        // use mock

        OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);

        streamWriter.close();

}

4.4. 验证模拟对象的调用

Mockito能够跟踪所有方法调用,以及这些方法给模拟对象设定的参数。你可以对模拟对象使用verify()方法来验证他们符合说明的条件。例如,你可以验证一个方法是否是在规定参数下被调用的。有的时候,这种类型的测试被称为行为测试。行为测试不会检查方法调用的结果,而是检查方法是否是在争取的参数下被调用的。

import static org.mockito.Mockito.*;

 

@Test

public void testVerify()  {

        // create and configure mock

        MyClass test = Mockito.mock(MyClass.class);

        when(test.getUniqueId()).thenReturn(43);

 

 

        // call method testing on the mock with parameter 12

        test.testing(12);

        test.getUniqueId();

        test.getUniqueId();

 

 

        // now check if method testing was called with the parameter 12

        verify(test).testing(Matchers.eq(12));

 

        // was the method called twice?

        verify(test, times(2)).getUniqueId();

 

        // other alternatives for verifiying the number of method calls for a method

        verify(mock, never()).someMethod("never called");

        verify(mock, atLeastOnce()).someMethod("called at least once");

        verify(mock, atLeast(2)).someMethod("called at least twice");

        verify(mock, times(5)).someMethod("called five times");

        verify(mock, atMost(3)).someMethod("called at most 3 times");

}

4.5. Wrapping Java objects with Spy

@Spy或是spy()可以用来包裹真实对象。除非特殊说明,每一次调用都被委托在对象身上。

 

 

import static org.mockito.Mockito.*;

// Lets mock a LinkedList

List list = new LinkedList();

List spy = spy(list);

 

//You have to use doReturn() for stubbing

doReturn("foo").when(spy).get(0);

 

// this would not work

// real method is called so spy.get(0)

// throws IndexOutOfBoundsException (list is still empty)

when(spy.get(0)).thenReturn(“foo");

 

verifyNoMoreInteractions()可以帮你你确认没有其他方法被调用。

 

4.6. 通过Mockito使用@InjectMocks进行依赖注入

 

你还可以使用@InjectMocks注解,它会尝试进行构造器、方法或是域以来的注入。例如,假定你拥有下面的类:

 

public class ArticleManager {

    private User user;

    private ArticleDatabase database;

 

    ArticleManager(User user) {

     this.user = user;

    }

 

    void setDatabase(ArticleDatabase database) { }

}

这个类可以通过Mockito来构建,它的依赖关系可以用模拟对象来满足。请参看下面的代码段:

 

@RunWith(MockitoJUnitRunner.class)

public class ArticleManagerTest  {

 

       @Mock ArticleCalculator calculator;

       @Mock ArticleDatabase database;

       @Most User user;

 

       @Spy private UserProvider userProvider = new ConsumerUserProvider();

 

       @InjectMocks private ArticleManager manager;

 

       @Test public void shouldDoSomething() {

               // assume that ArticleManager has a method called initialize which calls a method

               // addListener with an instance of ArticleListener

               manager.initialize();

 

           // validate that addListener was called

               verify(database).addListener(any(ArticleListener.class));

       }

}

 创建ArticleManager 实例,并且将模拟注入其中。更多信息请参阅: http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.

 4.7. 获取引数

 ArgumentCaptor类让你可以在验证期间获取方法调用的引数。字获取方法调用引数之后,你可以将它们用在测试中。

 import static org.hamcrest.Matchers.hasItem;

import static org.junit.Assert.assertThat;

import static org.mockito.Mockito.mock;

import static org.mockito.Mockito.verify;

 

import java.util.Arrays;

import java.util.List;

 

import org.junit.Rule;

import org.junit.Test;

import org.mockito.ArgumentCaptor;

import org.mockito.Captor;

import org.mockito.junit.MockitoJUnit;

import org.mockito.junit.MockitoRule;

 

 

public class MockitoTests {

 

        @Rule public MockitoRule rule = MockitoJUnit.rule();

 

        @Captor

    private ArgumentCaptor> captor;

 

 

        @Test

    public final void shouldContainCertainListItem() {

                List asList = Arrays.asList("someElement_test", "someElement");

        final List mockedList = mock(List.class);

        mockedList.addAll(asList);

 

        verify(mockedList).addAll(captor.capture());

        final List capturedArgument = captor.>getValue();

        assertThat(capturedArgument, hasItem("someElement"));

    }

}

4.8. 限制

Mockito也有一些限制,它无法对下列结构进行测试:

• 最终类(final classes)

• 匿名类(anonymous classes)

• 基本类型(primitive types)

5. 在安卓系统上使用Mockito

 Mockito可以直接用在安卓系统的单元测试上,方法是在Gradle开发文件上添加依赖,然后就可以在安卓测试中使用了。请注意,你还需要将dexmaker和dexmaker-mockito作为依赖添加到Gradle开发文件中。

 dependencies {

    testCompile 'junit:junit:4.12'

    // required if you want to use Mockito for unit tests

    testCompile 'org.mockito:mockito-core:1.+'

    // required if you want to use Mockito for Android instrumentation tests

    androidTestCompile 'org.mockito:mockito-core:1.+'

    androidTestCompile "com.google.dexmaker:dexmaker:1.2"

    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"

}

6. 练习:利用Mockito编写仪表单元测试

6.1. 在安卓系统内在测试下创建程序

使用名叫com.vogella.android.testing.mockito.contextmock的包来创建安卓程序。请参考下面的例子,添加一个允许使用特定参数创建意图的静态方法。

 public static Intent createQuery(Context context, String query, String value) {

        // Reuse MainActivity for simplification

    Intent i = new Intent(context, MainActivity.class);

    i.putExtra("QUERY", query);

    i.putExtra("VALUE", value);

    return i;

}

6.2. 给应用/build.gradle文件添加Mockito依赖

 dependencies {

    // the following is required to use Mockito and JUnit for your

    // instrumentation unit tests on the JVM

        androidTestCompile 'junit:junit:4.12'

    androidTestCompile 'org.mockito:mockito-core:2.0.57-beta'

    androidTestCompile 'com.android.support.test:runner:0.3'

    androidTestCompile "com.google.dexmaker:dexmaker:1.2"

    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"

 

    // the following is required to use Mockito and JUnit for your unit

    // tests on the JVM

    testCompile 'junit:junit:4.12'

    testCompile 'org.mockito:mockito-core:1.+'

 

}

6.3. 创建测试

 使用Mockito来创建新测试,确定这个测试是在正确的额外数据中所触发。

为此,你需要使用Mockito模拟Context对象,具体请参看下面例子。

 package com.vogella.android.testing.mockitocontextmock;

 

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

 

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.Mockito;

 

import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertNotNull;

 

public class TextIntentCreation {

 

    @Test

    public void testIntentShouldBeCreated() {

        Context context = Mockito.mock(Context.class);

        Intent intent = MainActivity.createQuery(context, "query", "value");

        assertNotNull(intent);

        Bundle extras = intent.getExtras();

        assertNotNull(extras);

        assertEquals("query", extras.getString("QUERY"));

        assertEquals("value", extras.getString("VALUE"));

    }

}

7. 练习:使用Mockito创建模拟对象

 7.1. 目标

使用Mockito创建一个可以模拟的API

7.2. 创建示例Twitter API

实施一个TwitterClient,它需要与ITweet实例相互协作。但是假设这些ITweet实例非常难以获取,例如使用了复杂的服务。

 public interface ITweet {

 

        String getMessage();

}

public class TwitterClient {

 

        public void sendTweet(ITweet tweet) {

                String message = tweet.getMessage();

 

                // send the message to Twitter

        }

}

7.3. 模拟ITweet实例

为了避免使用复杂服务来获取ITweet实例,你也可以用Mockito对其进行模拟。

 @Test

public void testSendingTweet() {

        TwitterClient twitterClient = new TwitterClient();

 

        ITweet iTweet = mock(ITweet.class);

 

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

 

        twitterClient.sendTweet(iTweet);

}

Now the TwitterClient can make use of a mocked ITweet instance and will get "Using Mockito is great" as message when calling getMessage() on the mocked ITweet.

7.4. Verify method invocation

Ensure that getMessage() is at least called once.

@Test

public void testSendingTweet() {

        TwitterClient twitterClient = new TwitterClient();

 

        ITweet iTweet = mock(ITweet.class);

 

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

 

        twitterClient.sendTweet(iTweet);

 

        verify(iTweet, atLeastOnce()).getMessage();

}

 7.5. 验证

运行测试,验证测试已经成功。

8. 模拟静态方法

8.1. 用于模拟静态方法的Powermock

Mockito无法模拟静态方法。要想达到这个目的你可以使用Powermock。

 import java.net.InetAddress;

import java.net.UnknownHostException;

 

public final class NetworkReader {

    public static String getLocalHostname() {

        String hostname = "";

        try {

            InetAddress addr = InetAddress.getLocalHost();

            // Get hostname

            hostname = addr.getHostName();

        } catch ( UnknownHostException e ) {

        }

        return hostname;

    }

}

编写可以模拟NetworkReader并将其作为以来的测试,你可以使用下面的代码段:

 import org.junit.runner.RunWith;

import org.powermock.core.classloader.annotations.PrepareForTest;

 

@RunWith( PowerMockRunner.class )

@PrepareForTest( NetworkReader.class )

public class MyTest {

 

// Find the tests here

 

 @Test

public void testSomething() {

    mockStatic( NetworkUtil.class );

    when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );

 

    // now test the class which uses NetworkReader

}

===

 

 

== Using a wrapper instead of Powermock

 Mockito资源:

 Mockito home page

 Mockito project hosting page

  Mockito release notes

 Martin Fowler about Mocks, Stubs etc.

 Chiu-Ki Chan Advanced Android Espresso presentation

原    文:Unit tests with Mockito - Tutorial
译    文:SDK.cn专栏
作    者:Christian(编译)
评论