【问题标题】:Testing with Mockito for Android使用 Mockito for Android 进行测试
【发布时间】:2017-11-27 18:20:23
【问题描述】:

您能否解释一下如何获得测试覆盖率?我正在寻求帮助,因为 Mockito 完全违反直觉。如果我只使用模拟对象,那么我怎么能获得测试覆盖率?我不明白。

这是我的测试。

package dev.game.adventure;


import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.widget.TextView;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AdventureTest {

    private static final String FAKE_STRING = "HELLO WORLD";

    @Mock
    Simulation engine;

    @Mock
    AdventureWorld mWorld;

    @Mock
    Adventure mworld;

    @Mock
    Context aContext;

    @Mock
    FullscreenActivity mActivity;

    @Mock
    Drawable mDrawable;

    @Mock
    Resources mResources;

    @Mock
    Place mPlace;

    @Mock
    AdventureGame ag;


    @Test
    @Ignore
    public void Tedye() {
        //when(mWorld.defaultPlace()).thenReturn(mPlace);
        // myObjectUnderTest.wakeMeAfter(new WalkingPerson(myObjectUnderTest, mWorld, "new", 2, mActivity), 10);
        //String result2 = myObjectUnderTest.getHelloWorldString();
        //assertThat(result2, is(FAKE_STRING));}  createPlace("Heaven", target, R.mipmap.dungeon2);
        //    Adventure a = new Adventure(textview, mactivity, ag);

    }


    @Test
    @Ignore
    public void testd() {
//           Textview scrollable = ''''''''''''(R.id.textView1);

        when(mWorld.defaultPlace()).thenReturn(mPlace);
        Context mCont;

    }


    @Test
    @Ignore
    public void adventureWorld() {
        // Simulation myObjecUnderTest = new Simulation();
        Adventure a = new Adventure(new TextView(aContext), mActivity, ag);
        Player p = a.getPlayer();
        p.say("foobar", mActivity);
        p.say("Hello my name is " + a.getPlayer().getMe().name, mActivity);

    }
    //@Ignore
    @Test
    public void adventureTest() {
        Simulation myObjectUnderTest = new Simulation();
        when(mWorld.defaultPlace()).thenReturn(mPlace);
        myObjectUnderTest.wakeMeAfter(new WalkingPerson(myObjectUnderTest, mWorld, "new", 2, mActivity), 10);
        String result2 = myObjectUnderTest.getHelloWorldString();
        assertThat(result2, is(FAKE_STRING));
    }
    //@Ignore
    @Test
    public void personTest() {
        Simulation myObjectUnderTest = new Simulation();
        when(mResources.getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mActivity.getResources()).thenReturn(mResources);
        when(mActivity.getResources().getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mWorld.defaultPlace()).thenReturn(mPlace);
        WalkingPerson myObjectUnderTest2 = new WalkingPerson(myObjectUnderTest, mWorld, "blaha", 2, mActivity);
        String result2 = myObjectUnderTest2.getHelloWorldString();
        myObjectUnderTest2.getThings();
        myObjectUnderTest2.getWorld();
        assertThat(result2, is(FAKE_STRING));
    }
    //@Ignore
    @Test
    public void trollTest() {
        Simulation myObjectUnderTest = new Simulation();
        AdventureWorld ag;
        when(mResources.getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mActivity.getResources()).thenReturn(mResources);
        when(mActivity.getResources().getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mWorld.defaultPlace()).thenReturn(mPlace);
        WalkingPerson myObjectUnderTest2 = new Troll(myObjectUnderTest, mWorld, "Loki", mActivity);
        String result2 = myObjectUnderTest2.getHelloWorldString();
        myObjectUnderTest2.getThings();
        AdventureWorld adv = (AdventureWorld) myObjectUnderTest2.getWorld();
        //assertThat(adv.defaultPlace().toString().equals(mWorld.defaultPlace().toString()));
        // assertThat(adv.defaultPlace(), is(FAKE_STRING));
        assertThat(myObjectUnderTest2.getName(), is("Loki"));
        //assertThat(adv.messsage, is(FAKE_STRING));
    }
    //@Ignore
    @Test
    public void cokeTest() {
        when(mWorld.getPlace("Dungeon")).thenReturn(mPlace);
        mWorld.getPlace("Dungeon").addThing(new CocaCola("Ljummen cola"));
        Simulation myObjectUnderTest = new Simulation();
        when(mResources.getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mActivity.getResources()).thenReturn(mResources);
        when(mActivity.getResources().getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        when(mWorld.defaultPlace()).thenReturn(mPlace);
        WalkingPerson myObjectUnderTest2 = new Troll(myObjectUnderTest, mWorld, "blaha", mActivity);
        String result2 = myObjectUnderTest2.getHelloWorldString();
        myObjectUnderTest2.getThings();
        myObjectUnderTest2.getWorld();
        assertThat(result2, is(FAKE_STRING));
    }
    @Ignore
    @Test
    public void testPlace() {
        Simulation myObjectUnderTest = new Simulation();
        when(mResources.getDrawable(R.mipmap.dungeon)).thenReturn(mDrawable);
        mWorld.createPlace("Heaven", mActivity, R.mipmap.dungeon2);
        mWorld.createPlace("Hell", mActivity, R.mipmap.dungeon2);
        mWorld.connect("Heaven", "Hell", "Down", "Up");
        mWorld.randomPlace();
        assertTrue(false);

    }
    @Ignore
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
      //  Context appContext = InstrumentationRegistry.getTargetContext();
       // assertEquals("dev.game.adventure", appContext.getPackageName());
    }
}

存储库是available online

【问题讨论】:

  • 一种方法是有选择地使用模拟,以测试您无法使用被测真实代码轻松测试的情况。例如,您在Whatever 类上有一个something() 方法,该方法可以返回一个不包含Swot 对象的Frobozz 实例,但您不能轻易创建一个Whatever这样做(因为它只发生在闰日的雨中)。因此,您创建了一个模拟 Whatever,其中 something() 返回此特定结果以进行测试,否则您通常测试 Whatever
  • 我觉得自己很愚蠢,我不明白。当我使用模拟对象时,我得到 0 % 的代码覆盖率。
  • 您对被模拟的事物的覆盖率为 0%。您创建模拟来测试其他代码需要处于特定状态的对象,模拟是获取此类对象的最简单方法。或者,您创建模拟来记录该模拟上其他代码调用方法的结果。您使用模拟的目标是帮助您为其他代码编写测试并提高其他代码的覆盖率。
  • 在 Android 中模拟的另一个原因是能够运行单元测试(在 JVM 上运行)而不是仪器测试(在 Android 中运行),就像在单元测试中一样,有不使用 ContextTextView。所以,你模拟这些,所以你自己的测试代码(和生产代码)可以在 JVM 上运行,同时与模拟交互。您没有尝试测试Context,因为您没有编写Context。您正在使用模拟 Context 来测试碰巧使用 Context其他代码
  • 我知道的越多,我知道的越少。当我使用模拟对象时,我仍然不明白如何获得测试覆盖率。

标签: android mocking mockito


【解决方案1】:

我写了一个快速示例,说明如何应用模拟来帮助进行单元测试。希望这可以帮助解决问题。

在此示例中,我有两个名为 SampleClassValidator 的主要类,其想法是 SampleClass 依赖于 Validator 的实例来帮助执行其操作。

SampleClass:

//This class's only function is to return one string if the username it's given is valid, and another string if its not valid. It does this with the help of the Validator class

public class SampleClass {

    private final Validator validator;

    public SampleClass(Validator v){
        this.validator = v;
    }

    public String createWelcomeMessage(final String username){

        if(this.validator.isValid(username)){
            return "Welcome, " + username + "!";
        }

        return username + " is not a valid username.";

    }

}

验证器:

//This class's only job is to validate strings. Right now, all it does is check the length, and fail if its > 15 characters.
public class Validator {

    public boolean isValid(final String str) {

        if(str.length() > 15){
            return false;
        }

        return true;
    }

}

假设我想为SampleClass 编写一些单元测试。单元测试通常应该测试小的、集中的代码单元,我不希望Validator 的具体细节渗入这些测试。我真的只关心Validator 类的输出如何影响SampleClass,以及SampleClass 实现的功能。这就是模拟(和 Mockito)的用武之地,因为 我可以模拟与 Validator 的交互以单独测试 SampleClass 的行为。

SampleClassTest:

public class SampleClassTest {

    //Mock the validator class, which SampleClass depends on.
    private Validator validator = Mockito.mock(Validator.class);

    //Do not mock the SampleClass class. Create a new one, and use the mocked validator within it.
    private SampleClass sampleClass = new SampleClass(validator);


    //Test that a valid username will print out the message "Welcome, username!"
    @Test
    public void testValidUsername(){

        Mockito.when(validator.isValid(Mockito.anyString())).thenReturn(true);
        final String message = this.sampleClass.createWelcomeMessage("testuser");

        Assert.assertEquals("Welcome, testuser!", message);

    }


    //Test that an invalid username will print out the message "username is not a valid username"
    @Test
    public void testInvalidUsername(){

        Mockito.when(validator.isValid(Mockito.anyString())).thenReturn(false);

        final String message = this.sampleClass.createWelcomeMessage("thisiswaywaywaytoolong");
        Assert.assertEquals("thisiswaywaywaytoolong is not a valid username.", message);
    }

}

使用 jacoco 来查看单元测试覆盖率,SampleClass 被完全覆盖,Validator 被完全覆盖,因为现在它被模拟和未经测试。我应该单独编写针对 Validator(unmocked!) 的额外单元测试。

将其应用到您的项目中:

例如,考虑go method on your Person class。似乎您可以为此方法编写一些单元测试,并对FullscreenActivityWorld 对象使用Mocks,并验证该方法在各种条件下是否正确操作World 对象。这将通过一些单元测试覆盖Person->go,并以Person 测试与您的其他类隔离的方式这样做。这在处理 Android 小部件等(如 CommonsWare 提到的)时也非常有用,因为您可以模拟它,而不是在测试中处理复杂或不切实际的类。

TL;DR

  • 使用模拟来模拟对其他类/对象的依赖关系,这些类/对象不是特定单元测试的重点。特别复杂或不切实际的类(CommonsWare 在 cmets 中确实解释得很好)
  • 您不应该使用模拟,而是应该使用模拟来帮助您为未模拟的类/对象编写单元测试

【讨论】:

    猜你喜欢
    • 2020-02-22
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多