Android中的@SmallTest,@MediumTest和@LargeTest注解的目的是什么?

Android中的@SmallTest,@MediumTest和@LargeTest注解的目的是什么?

例如:

@SmallTest
public void testStuff() {
    TouchUtils.tapView(this, anEditTextView);
    sendKeys("H E L P SPACE M E PERIOD");
    assertEquals("help me.", anEditTextView.getText().toString());
}

参考下图:

继续阅读Android中的@SmallTest,@MediumTest和@LargeTest注解的目的是什么?

Robolectric 3.x编写屏幕分辨率/多语言/资源文件相关测试用例

在编写 Android 测试用例的时候,有时候我们需要涉及到屏幕分辨率相关测试用例。

比如不同分辨率得到不同的像素数值,可以参考如下:

@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@Config(sdk = Build.VERSION_CODES.P)
public class DimensUnitTest {

    @Test
    @Config(qualifiers = "w480dp-h800dp")
    public void dimens800x480_isCorrect() {
        final Context context = RuntimeEnvironment.application;
        final double ref100DpBase = 42.666667;

        float expectDp = 110;
        double dips = context.getResources().getDimension(R.dimen.dimen_110);

        assertEquals(ref100DpBase * expectDp / 100, dips, delta);

        for (Map.Entry<Integer, Double> entry : expectDimens.entrySet()) {
            dips = context.getResources().getDimension(entry.getKey());
            assertEquals(ref100DpBase * entry.getValue() / 100, dips, delta);
        }
    }
	
}

比如不同语言得到不同的字符串,可以参考如下:

@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@Config(sdk = Build.VERSION_CODES.P)
public class LangUnitTest {

    /**
     * 使用qualifiers加载对应的资源文件
     *
     * @throws Exception
     */
    @Config(qualifiers = "zh-rCN")
    @Test
    public void testString() throws Exception {
        final Context context = RuntimeEnvironment.application;
        assertThat(context.getString(R.string.app_name), is("单元测试Demo"));
    }
	
}

其他相关的测试参数,参考 Device Configuration

注意需要在 build.gradle 中增加资源包含信息,否则在测试的时候会找不到指定的资源文件,默认只测试代码,被测试的资源文件不打包进入应用。

参考如下:

    testOptions {
        unitTests {
            includeAndroidResources = true
            all {
                //命令行下 单元测试可能卡住的问题
                jvmArgs '-noverify'
                //robolectric外部指定下载资源链接的参数,使用 -D 参数指定 bash gradlew clean build -Drobolectric.dependency.repo.url=http://127.0.0.1/jcenter
                systemProperty 'robolectric.dependency.repo.url', System.getProperty("robolectric.dependency.repo.url")
                systemProperty 'robolectric.dependency.repo.id', System.getProperty("robolectric.dependency.repo.id")
            }
        }
    }

参考链接


Android Studio 3.6.3/4.0/4.1/4.2配置Robolectric-3.8/4.3.1/4.5.1/4.6.1 Powermock-1.6.6单元测试环境

基础配置以及常见错误


目前版本的Android Studio 3.6.3/4.0/4.1/4.2使用Robolectric-3.8/4.3.1/4.5.1/4.6.1,只能配合Powermock-1.6.6,不能高于这个版本PowerMock ,尽管软件源中的版本已经更新升级到2.0.7版本了,但是Robolectric并没有及时更新依赖。尝试过使用最新版本的PowerMock,结果很多莫名的报错。

    // 单元测试
    testImplementation "com.android.support.test:runner:1.0.2"
    testImplementation "junit:junit:4.13.2"
    testImplementation "org.robolectric:robolectric:4.5.1"

    // 注意AndroidX环境下,不能引入shadows-supportv4,Jetifier转换的时候报错
    testImplementation "org.robolectric:shadows-supportv4:4.5.1"

    // MultiDex支持
    testImplementation "org.robolectric:shadows-multidex:4.5.1"
    
    // 此处版本更新需要特别小心,目前 robolectric 跟 powermock 存在兼容问题,
    // 需要固定的某些版本才能正常配合
    // 依赖关系参考 
    // https://github.com/robolectric/robolectric/wiki/Using-PowerMock
    testImplementation "org.powermock:powermock-module-junit4:1.6.6"
    testImplementation "org.powermock:powermock-module-junit4-rule:1.6.6"
    testImplementation "org.powermock:powermock-classloading-xstream:1.6.6"
    testImplementation "org.powermock:powermock-api-mockito:1.6.6"

如果 Windows 系统下出现如下提示,受限于Windows系统路径不能超过260个字符的限制

13:36 Error running 'xxxTest': Command line is too long. Shorten command line for xxxTest or also for Android JUnit default configuration.

貌似Windows 10 Build 14352版中已经可以通过修改注册表去除260字符的路径长度限制了。

在项目`/.idea/workspace.xml`文件中添加一行代码如下

<component name="PropertiesComponent">
    ...
    <property name="dynamic.classpath" value="true" />
</component>

对于使用MultiDex的应用,如果报告如下错误:

java.lang.RuntimeException: MultiDex installation failed (/var/folders/h6/4__l0pyn32j1mkkysynnxydr0000gn/T/robolectric-Method_xxxxxxxxxxxx_isCorrect4035485067136008322/com.xxxx.xxxx.app-sourceDir (Is a directory)).

	at android.support.multidex.MultiDex.install(MultiDex.java:121)
	at com.xxx.xx.app.xx.application.xxxxxx.attachBaseContext(MyApplication.java:212)
	at android.app.Application.attach(Application.java:212)
	at org.robolectric.util.ReflectionHelpers.callInstanceMethod(ReflectionHelpers.java:283)
	at org.robolectric.shadows.ShadowApplication.callAttach(ShadowApplication.java:79)
	at org.robolectric.android.internal.AndroidTestEnvironment.installAndCreateApplication(AndroidTestEnvironment.java:250)
	at org.robolectric.android.internal.AndroidTestEnvironment.setUpApplicationState(AndroidTestEnvironment.java:169)
	at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:301)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:243)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

需要在`build.gradle`中引入

 
    // MultiDex支持
    testImplementation "org.robolectric:shadows-multidex:4.5.1"

例子测试代码:

package com.mobibrw.example;

import org.junit.Test;
import static org.junit.Assert.*;

import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.multidex.ShadowMultiDex;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore("jdk.internal.reflect.*")
@Config(shadows = ShadowMultiDex.class)
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}

注意,需要

@Config(shadows = ShadowMultiDex.class)

才能使得配置生效。

如果发生如下错误:

java.lang.VerifyError: Expecting a stackmap frame at branch target 43
Exception Details:
  Location:
    com/meizu/cloud/pushsdk/base/IntentReceiver.onReceive(Landroid/content/Context;Landroid/content/Intent;)V @6: ifne
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: b200 08b6 0013 9a00 25bb 0004 5912 0110
    0x0000010: 0ab7 000e 59b3 000a b600 10bb 0003 59b2
    0x0000020: 000a b600 0fb7 000c b300 09b2 0009 bb00
    0x0000030: 0659 2a2b 2cb7 0011 b600 0d57 b1       


	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
	at java.lang.Class.getConstructor0(Class.java:3075)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:396)
	at org.robolectric.internal.bytecode.ShadowImpl.newInstanceOf(ShadowImpl.java:18)
	at org.robolectric.shadow.api.Shadow.newInstanceOf(Shadow.java:35)
	at org.robolectric.android.internal.AndroidTestEnvironment.registerBroadcastReceivers(AndroidTestEnvironment.java:541)
	at org.robolectric.android.internal.AndroidTestEnvironment.installAndCreateApplication(AndroidTestEnvironment.java:265)
	at org.robolectric.android.internal.AndroidTestEnvironment.setUpApplicationState(AndroidTestEnvironment.java:169)
	at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:301)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:243)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

继续阅读Android Studio 3.6.3/4.0/4.1/4.2配置Robolectric-3.8/4.3.1/4.5.1/4.6.1 Powermock-1.6.6单元测试环境

Android Studio 2.1.3配置Robolectric-3.0,Powermock-1.6.5单元测试环境

Android Studio提供了比较方便的单元测试,但是由于Android系统的限制(ClassloaderGoogle自己实现的,Powermock无法修改底层的bytecode),目前Powermock还没办法直接在设备上执行测试,但是我们代码中难免存在一些静态对象,需要测试的时候,只能求助于PowermockRobolectric的组合。
具体的配置如下:
1.首先在需要测试的项目的build.gradle中声明需要使用PowermockRobolectric

testCompile 'junit:junit:4.12'
testCompile 'org.powermock:powermock-api-mockito:1.6.5'
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.5'
testCompile 'org.powermock:powermock-module-junit4:1.6.5'
testCompile "org.powermock:powermock-classloading-xstream:1.6.5"
/*不可使用3.1.x版本,3.1.x版本无法与PowerMock配合测试,运行时候直接报错*/
testCompile 'org.robolectric:robolectric:3.0'

2.定义测试基类,在基类中声明一些必备的设置
AbsRobolectricPowerMockTest.java

import android.os.Build;

import org.junit.Rule;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;

/**
 * Created by longsky.wq on 2016/8/22.
 */
@PowerMockRunnerDelegate(RobolectricGradleTestRunner.class)
@RunWith(PowerMockRunner.class)
@Config(constants = BuildConfig.class,
        sdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*","javax.crypto.","java.security.*"})
abstract public class AbsRobolectricPowerMockTest {
    @Rule
    public PowerMockRule rule = new PowerMockRule();
}

3.定义真正的测试子类
DeckardActivityTest.java

import org.junit.Test;
import org.junit.Assert;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;

import static junit.framework.Assert.assertTrue;

@PrepareForTest(Static.class)
public class DeckardActivityTest extends AbsRobolectricPowerMockTest {
    @Test
    public void testStaticMocking() {
        /*如果如下断言失败,说明Robolectric环境没有初始化成功,说明配置存在问题*/
        Assert.assertNotNull(RuntimeEnvironment.application);
        
        PowerMockito.mockStatic(Static.class);
        Mockito.when(Static.staticMethod()).thenReturn("hello mock");

        assertTrue(Static.staticMethod().equals("hello mock"));
    }
}

注意,参考链接中的内容不可完全相信,按照参考链接中的配置(Robolectric-3.1.2+PowerMock-1.6.5),一般会遇到如下问题(看上去很怪异,其实是由于不同的classloader同时加载了相同的类,导致尽管类名是相同的,但是依旧无法进行类型转换):

com.thoughtworks.xstream.converters.ConversionException: Could not call org.apache.tools.ant.Project$AntRefTable.readObject() : Cannot convert type org.apache.tools.ant.Project to type org.apache.tools.ant.Project
---- Debugging information ----
class               : org.apache.tools.ant.types.FileSet
required-type       : org.apache.tools.ant.types.FileSet
converter-type      : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path                : /org.powermock.modules.junit4.rule.PowerMockStatement$1/outer-class/fNext/this$1/outer-class/dependencyResolver/dependencyResolver/project/references/hashtable/org.apache.tools.ant.types.FileSet/project
line number         : 167
-------------------------------
message             : Could not call org.apache.tools.ant.Project$AntRefTable.readObject()
cause-exception     : com.thoughtworks.xstream.converters.ConversionException
cause-message       : Cannot convert type org.apache.tools.ant.Project to type org.apache.tools.ant.Project
class               : org.apache.tools.ant.Project$AntRefTable
required-type       : org.apache.tools.ant.types.FileSet
converter-type      : com.thoughtworks.xstream.converters.reflection.SerializableConverter
path                : /org.powermock.modules.junit4.rule.PowerMockStatement$1/outer-class/fNext/this$1/outer-class/dependencyResolver/dependencyResolver/project/references/hashtable/org.apache.tools.ant.types.FileSet/project
line number         : 167
class[1]            : org.apache.tools.ant.Project
converter-type[1]   : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
class[2]            : org.robolectric.internal.dependency.MavenDependencyResolver
class[3]            : org.robolectric.internal.dependency.CachedDependencyResolver
class[4]            : org.robolectric.RobolectricGradleTestRunner
class[5]            : org.robolectric.RobolectricTestRunner$HelperTestRunner
class[6]            : org.robolectric.RobolectricTestRunner$HelperTestRunner$1
class[7]            : org.powermock.modules.junit4.rule.PowerMockStatement
class[8]            : org.powermock.modules.junit4.rule.PowerMockStatement$1
version             : not available
-------------------------------

	at com.thoughtworks.xstream.core.util.SerializationMembers.callReadObject(SerializationMembers.java:133)
	at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doUnmarshal(SerializableConverter.java:455)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:480)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:412)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:50)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:134)
	at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:32)
	at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:1206)
	at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:1190)
	at com.thoughtworks.xstream.XStream.fromXML(XStream.java:1061)
	at com.thoughtworks.xstream.XStream.fromXML(XStream.java:1052)
	at org.powermock.classloading.DeepCloner.clone(DeepCloner.java:54)
	at org.powermock.classloading.AbstractClassloaderExecutor.executeWithClassLoader(AbstractClassloaderExecutor.java:56)
	at org.powermock.classloading.SingleClassloaderExecutor.execute(SingleClassloaderExecutor.java:67)
	at org.powermock.classloading.AbstractClassloaderExecutor.execute(AbstractClassloaderExecutor.java:43)
	at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:75)
	at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:250)
	at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:176)
	at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:142)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner$2.call(DelegatingPowerMockRunner.java:148)
	at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner$2.call(DelegatingPowerMockRunner.java:140)
	at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.withContextClassLoader(DelegatingPowerMockRunner.java:131)
	at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.run(DelegatingPowerMockRunner.java:140)
	at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121)
	at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
	at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
Caused by: com.thoughtworks.xstream.converters.ConversionException: Cannot convert type org.apache.tools.ant.Project to type org.apache.tools.ant.Project
---- Debugging information ----
class               : org.apache.tools.ant.types.FileSet
required-type       : org.apache.tools.ant.types.FileSet
converter-type      : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path                : /org.powermock.modules.junit4.rule.PowerMockStatement$1/outer-class/fNext/this$1/outer-class/dependencyResolver/dependencyResolver/project/references/hashtable/org.apache.tools.ant.types.FileSet/project
line number         : 167
-------------------------------
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:439)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:263)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:50)
	at com.thoughtworks.xstream.converters.reflection.SerializableConverter$2.readFromStream(SerializableConverter.java:337)
	at com.thoughtworks.xstream.core.util.CustomObjectInputStream.readObjectOverride(CustomObjectInputStream.java:120)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:364)
	at java.util.Hashtable.readObject(Hashtable.java:996)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.thoughtworks.xstream.core.util.SerializationMembers.callReadObject(SerializationMembers.java:125)
	... 86 more

参考链接


Beyond Mocking with PowerMock

PowerMock is a Java framework that allows you to for example mock static and final methods, final classes and construction of new objects. PowerMock is thus a powerful framework and it's quite easy to misuse it unless you know what you're doing. What I've seen time and again is that people are using mocking when what they probably should be doing is stubbing. With all the powerful features in PowerMock it can easily lead to complicated and hard-to-maintain test code.

Mocking vs Stubbing


So what's the difference and why does it matter? Mocking can be summarized more in terms of a specification:

1. Setup how your mocks should behave in your test. This means creating a specification for the mocks involved in this particular test.
2. Perform the actual test
3. Verify that the interactions with the mocks matched the specification (created during setup)

If a mocked method is called more or less times than what's defined in the specification the test will fail. I would argue that most often you don't need this kind of rigorous approach. Sure you may need a way to specify that a method (let's call it X) doesn't call a third-party system but instead having it return some pre-defined value. It doesn't follow that it's always interesting to verify that X was called a particular number of times with with some exact arguments. Sometimes it's legitimate and useful, but as long as the result of the method we're testing behaves as expected (for example that it returns the expected result), the call to X is less important and may be regarded as an implementation detail.

Stubbing on the other hand doesn't require a specification to be fulfilled. What you do is to something like this:

1. Define what the collaborator methods should return when you run your test
2. Perform the actual test

Now if a collaborator happens to be called more than once, or perhaps not at all, doesn't really matter as long as the end result of the actual test behaves as expected.

So what does all this has to do with PowerMock? Well PowerMock has something called a “stubbing” API that let's you do quite many things without having to revert to mocking. You don't actually have to depend on any third-party mocking API either (like EasyMock and Mockito).

Stubbing, suppressing and replacing with PowerMock


If all you need to do is stubbing a non-static public collaborator method you don't need PowerMock. You're probably better off with just vanilla Mockito or EasyMock. How ever if you want to stub a static or private method, suppressing a constructor or replacing a method PowerMock can help!

Stubbing


So first consider stubbing a method with PowerMock. Consider the following simple Java class:

public class MyClass1 {
    static String hello(String arg) {
        return "Hello " + arg;
    }
    private String goodbye(String arg) {
        return "Goodbye " + arg;
    }
}

Let's say we want to stub the static hello method to always return an expected value using the PowerMock stubbing API:

stub(method(MyClass1.class, "hello")).toReturn("Hello World");

Which means that a call to the hello method like this:

MyClass1.hello("John Doe");

will now always return the string "Hello World".

Note that stub is statically imported from org.powermock.api.support.membermodification.MemberModifier and method from org.powermock.api.support.membermodification.MemberMatcher. You would also have to prepare MyClass1 for test using @PrepareForTest(MyClass1.class) and use the PowerMock runner
@RunWith(PowerMockRunner.class).
Stubbing a private method looks very similar:

stub(method(MyClass1.class, "goodbye")).toReturn("Something");

Suppressing


You can also suppress methods, constructors and fields that you're not interested in. Essentially what this means is that “this piece of code doesn't do anything useful for this particular test case, just get it out of the way”. For example consider the following class:

public class MyClass2 {
    MyClass2() {
        System.load("some.dll");
    }
    static String hello(String arg) {
        return "Hello " + arg;
    }
}

When unit testing the made-up hello method there's no need for us to load the “some.dll” so let's get rid of it:

suppress(constructor(MyClass2.class));

It’s also possible to suppress all constructors of a class:

suppress(constructorsDeclaredIn(SomeClass.class));

or all methods:

suppress(methodsDeclaredIn(SomeClass.class));

In this case all methods declared in SomeClass will return a default value.

You can also essentially suppress an entire class, meaning all methods, constructors and fields:

suppress(everythingDeclaredIn(SomeClass.class));

Replacing


What if we only want to stub the hello method in MyClass1 when the parameter arg is equal to “John”? For all other arguments we like to invoke the original method. We can achieve this by replacing the method with an InvocationHandler like this:

replace(method(MyClass1.class, "hello")).with(
	new InvocationHandler() {
		public Object invoke(Object object, Method method,Object[] arguments) throws Throwable {
			if (arguments[0].equals("John")) {
				return "Hello John, you are awesome!";
			} else {
				return method.invoke(object, arguments);
			}
		}
});

This means that if you invoke MyClass1.hello("Someone") it'll return Hello Someone but calling it with John will return Hello John, you are awesome!.

You can also replace a method with another method! For example you may want to replace all log outputs in an integration test with your own method that simply prints everything to the console. Consider the following example:

public class MyClass3 {
	static String hello(String arg) {
		Logger.debug("Calling method hello with: "+arg);
		return "Hello " + arg;
	}
}

Imagine that the Logger.debug(..) method logs to disk but in our test we simply want to print to the console. We could implement a new class for this:

public class ConsoleLogger {
	public static void print(String arg) {
		System.out.println("Method called with arg: "+arg);
	}
}

And replace the Logger.debug(..) method with the ConsoleLogger.print(..) method in our test case:

replace(method(Logger.class, "debug")).with(method(ConsoleLogger.class, "print"))

This means that all calls to Logger.debug(..) will be replaced with ConsoleLogger.print(..). Note that this only works for static methods!

(Also note that in a real-life scenario there are most likely better ways to solve this problem, e.g. by simply configuring the original Logger to print to the console during our integration test).

Conclusion


As you've hopefully seen there are more to PowerMock than just mocking. For example it's often better to simply stub third party api calls than to mock them. PowerMock has good support for doing this in a simple way even for legacy systems. As always PowerMock should be used with care so whenever you find the urge to use PowerMock make sure you look into possible alternatives as well.

原文链接:Beyond Mocking with PowerMock

使用 Mockito 单元测试

目录


1. 需求知识

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

2.2. 存根(Stub) vs. 模拟对象 (Mock)

2.3. 行为测试 vs. 状态测试

2.4. 生成模拟对象

3. 模拟框架( Mock Framework)

4. Mockito

4.1. 使用 Mockito 模拟对象

4.2. 使用 Mockito

4.3. Mockito的限制

4.4. 模拟对象的配置

4.5. 验证模拟对象的行为

4.6. Spy

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

5.2. 安装

6. 链接和参考

1.需求知识

该教程需要理解单元测试和熟悉JUnit框架的使用。

如果您不熟悉JUnit,请阅读JUnit教程。

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

一个单元测试需要在隔离的环境下执行。如果可以的话需要消除其他依赖的服务影响。但实际上,软件中是充满依赖关系的.我们会基于service类写操作类,而service类又是基于数据访问类(DAOs)的,依次下去.

为了解决这个问题, 可以使用 存根 (Stub) 或者 模拟 (Mock) 对象的方法进行测试。

2.2. 存根(Stub) vs. 模拟对象 (Mock)

存根(Stub)类是实现了一个接口或者抽象类的类,可以在测试过程中使用该类,例如:

public class TestStub {
	static interface USB {
		void work();
	}

	static class Mp3Stub implements USB {
		@Override
		public void work() {
			// code.

		}
	 }

	static class Mp4Stub implements USB {
		@Override
		public void work() {
			// code.
		}
	}
}

一个模拟对象(mock object)是一个接口或者抽象类的虚拟实现。例如:

public class TestMock {
	static interface USB {
		void work();
	}

	@Test
	public void testMockObject() {
		USB usb = Mockito.mock( USB.class );
		usb.work();
	}
}

存根和模拟对象都可以传递给其他的对象进行测试。你的一些单元测试可以测这些类的正确性等。利用存根对象或者模拟对象可以保证测试过程中不受到其他的影响。

存根对象需要自定义实现方法;

模拟对象只需要更少的代码和简单的配置。

以下的内容将详细介绍模拟对象的使用方法。

2.3. 行为测试 vs. 状态测试

Mock 对象允许你对行为进行测试。有一些测试不需要验证结果,但是需要检查某些方法是否被正确的参数调用过。这种测试为行为测试。

状态测试只是关注与结果是否正确,而行为测试能够判断一个应用调用结构以及层次。

2.4. 生成模拟对象

你们可以使用Mock 框架来生成模拟对象。Mock 框架允许你在运行期间创建对象,并且定义它的一些行为。

一个典型的例子就是使用模拟对象来模拟数据库DAO层。在生产环境上是使用运行的数据库,但是在单元测试环境中完全可以用模拟对象来模拟数据,确保单元测试的正确条件。这样就不需要依赖于外部的数据。

3. 模拟框架( Mock Framework)

比较流行的模拟框架有 EasyMock、jMock 和 Mockito。下面的列表是这些框架的链接。
# jMock
http://jmock.org/
# EasyMock
http://easymock.org/
# Mockito
http://mockito.org/

4. Mockito

4.1. 使用 Mockito 模拟对象

Mockito 是比较流行的模拟框架,可以与JUnit 联合起来测试。它允许你进行创建和配置模拟对象。

Mockito的官方网站: Mockito 主页.

4.2. 使用 Mockito

Mockito 支持使用 mock() 静态方法创建模拟对象。

同样也支持 @Mock注解方式,如果使用注解的方式,需要使用在初始化方法调用 MockitoAnnotation.InitMock( this ) 方法

例如,下面的例子就是使用 Mockito 进行对类 ClassToTest 的单元测试。

public class MockitoTest {
	@Mock
	MyDatabase databaseMock;

	@Before
	public void setUp() throws Exception {
		MockitoAnnotations.initMocks(this);
	}


	@Test
	public void testQuery() {
		// 需要测试的类
		ClassToTest t = new ClassToTest(databaseMock);
		// 调用方法
		boolean check = t.query("* from t");
		// 验证结果
		assertTrue(check);
		// 模拟对象是否调用了该方法
		Mockito.verify( databaseMock ).query("* from t");
	}
}

提示

可以使用静态导入方法调用方法 mock()

4.3. Mockito的限制

Mockito 以下的类型不能进行构造:

  • 终态类(final classes)
  • 匿名类(anonymous classes)
  • 基本数据类型(primitive types)

4.4. 模拟对象的配置

Mockito 可以使用 verify() 方法来确认某些方法是否被调用过.

when(....).thenReturn(....) 结构可以为某些条件给定一个预期的返回值.

@Test
public void testList() {
	List mock = Mockito.mock( List.class );
	Mockito.when( mock.get( 0 ) ).thenReturn( 1 );
	assertEquals( "预期返回1", 1, mock.get( 0 ) );
}

同样可以使用doReturn(object).when(kdskfsk).methodCall 结构

4.5. 验证模拟对象的行为

Mockito 跟踪了所有的方法调用和参数的调用情况。verify()可以验证方法的行为。

查看下面的例子:

@Test
public void testMap() {
	Map mock = Mockito.mock( Map.class );
	Mockito.when( mock.get( "city" ) ).thenReturn( "深圳" );
	// test code
	assertEquals( "城市测试", "深圳", mock.get( "city" ) );
	Mockito.verify(mock).get( Matchers.eq( "city" ) );
	Mockito.verify( mock, Mockito.times( 2 ) );
}

4.6. Spy

@Spy 或者方法 spy() 可以包含一个真实的对象. 每次调用,除非特出指定,委托给改真实对象的调用.

@Test
public void testSpy() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	list.add( "yes" );
	List spy = Mockito.spy(list);
	//You have to use doReturn() for stubbing
	assertEquals( "yes", spy.get( 0 ) );
	Mockito.doReturn("foo").when(spy).get(0);
	assertEquals( "foo", spy.get( 0 ) );
 }


@Test( expected = IndexOutOfBoundsException.class)
public void testSpy2() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	List spy = Mockito.spy(list);
	// this would not work
	// real method is called so spy.get(0)
	// throws IndexOutOfBoundsException (list is still empty)
	Mockito.when(spy.get(0)).thenReturn("foo");
	assertEquals( "foo", spy.get( 0 ) );
}

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

Mockito 同样也可以在安卓平台上进行测试。

5.2. 安装

在 Android 测试项目中使用 Mockito。添加下面的包到Android 测试项目的 libs 目录
https://mockito.googlecode.com/files/mockito-all-1.9.5.jar
http://dexmaker.googlecode.com/files/dexmaker-1.0.jar
http://dexmaker.googlecode.com/files/dexmaker-mockito-1.0.jar
接下来可以在你的测试项目中使用 Mockito 。

6. 链接和参考

Mockito 项目主页
Mockito 的依赖注入功能
Unit tests with Mockito - Tutorial
使用 Mockito 单元测试 – 教程