使用Robolectric和PowerMock进行领域单元测试,产生零%代码覆盖率

时间:2016-10-20 14:18:54

标签: unit-testing realm code-coverage robolectric powermock

我按照这里的说明操作:

设置我的领域单元测试。测试全部通过但是当我尝试生成代码覆盖率以推送到SonarQube时,覆盖率报告显示0%的覆盖率。当我执行标准的Android Studio代码覆盖率时(如答案中所述:How to get code coverage using Android Studio?),它会生成一个显示91%覆盖率的报告。然而,这种方法的问题在于它是一个html报告,并且没有办法让它生成可用于SonarQube的xml报告。

这是测试类的代码

@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({Realm.class, RealmConfiguration.class, RealmCore.class, RealmLog.class})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@Config(constants = BuildConfig.class, manifest =    "src/main/AndroidManifest.xml", sdk = 21)
public class DecisionTreeTest
{
    @Captor
    ArgumentCaptor<Realm.Transaction.Callback> realmCallbackCaptor;

    // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    private Context mockContext;

    private final byte[] fakeRealmKey = {
        -122, -115, -113, -111, -105, -104, -101, -99, -94, -93, -90, -87,
        -77, -74, -67, -66, -63, -61, -56, -53, -48, -47, -33, -31,
        -30, -28, -22, -17, -5, -3, -1, 3, 8, 11, 17, 18,
        21, 22, 27, 30, 40, 42, 51, 52, 53, 54, 57, 59,
        61, 63, 67, 70, 74, 76, 78, 85, 90, 91, 103, 108,
        113, 117, 119, 127
    };

    @Before
    public void setUp() throws Exception
    {
        // Setup Realm to be mocked. The order of these matters
        mockStatic(RealmCore.class);
        mockStatic(RealmLog.class);
        mockStatic(Realm.class);
        mockStatic(RealmConfiguration.class);

        this.mockContext = RuntimeEnvironment.application;

        Whitebox.setInternalState(
                Realm.class,
                "applicationContext",
                RuntimeEnvironment.application);

        /*
        Better solution would be just mock the RealmConfiguration.Builder class.
        But it seems there is some problems for powermock to mock it (static inner class).
        We just mock the RealmCore.loadLibrary(Context) which will be called by
        RealmConfiguration.Builder's constructor.
        */
        doNothing().when(RealmCore.class);
        RealmCore.loadLibrary(any(Context.class));
    }

    @Test(expected = DecisionTreeException.class)
    public void persistSurvey_DecisionTreeRealmNotEnabled_ThrowsException() throws Exception
    {
        DecisionTree decisionTree = createSimpleDecisionTree();
        Survey survey = decisionTree.getSurveyFromResource(R.raw.survey);
        decisionTree.persistSurvey(survey, null, null);
    }

    @Test(expected = DecisionTreeException.class)
    public void persistSurvey_NullAsFirstParam_ThrowsException() throws Exception
    {
        DecisionTree decisionTree = createRealmDecisionTree();
        decisionTree.persistSurvey(null, null, null);
    }

    @Test
    public void persistSurvey_SurveyAsFirstParam_ThrowsException() throws Exception
    {
        final Realm mockRealm = mock(Realm.class);
         when(Realm.getInstance(any(RealmConfiguration.class))).thenReturn(mockRealm);

        org.mockito.stubbing.Answer<Void> executeAnswer = new org.mockito.stubbing.Answer<Void>()
        {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable
            {
                ((Realm.Transaction) invocation.getArguments()[0]).execute(mockRealm);
                return null;
            }
        };

        doAnswer(executeAnswer)
            .when(mockRealm)
            .executeTransactionAsync(
                    any(Realm.Transaction.class),
                    any(Realm.Transaction.OnSuccess.class),
                    any(Realm.Transaction.OnError.class));

        DecisionTree decisionTree = createRealmDecisionTree();
        Survey survey = decisionTree.getSurveyFromResource(R.raw.survey);

        decisionTree.persistSurvey(survey, null, null);

        verify(mockRealm).executeTransactionAsync(
            any(Realm.Transaction.class),
            any(Realm.Transaction.OnSuccess.class),
            any(Realm.Transaction.OnError.class));
        verify(mockRealm).copyToRealmOrUpdate(any(Survey.class));
    }

    private DecisionTree createRealmDecisionTree()
    {
        return new DecisionTree.Builder()
            .setContext(mockContext)
            .setRealmKey(fakeRealmKey)
            .setRealmEnabled(true)
            .build();
    }

    private DecisionTree createSimpleDecisionTree()
    {
        return new DecisionTree.Builder()
            .setContext(RuntimeEnvironment.application)
            .build();
    }
}

我认为问题出在以下几行:

@Rule
public PowerMockRule rule = new PowerMockRule();

但是,如果删除此行,我会收到以下错误,尽管@PrepareForTest行未更改。:

org.powermock.api.mockito.ClassNotPreparedException: 
The class io.realm.internal.RealmCore not prepared for test.
To prepare this class, add class to the '@PrepareForTest' annotation.
In case if you don't use this annotation, add the annotation on class or  method level.

1 个答案:

答案 0 :(得分:2)

我猜你正在使用Jacoco来收集报道。 PowerMock有一个已知的issue与Jacoco / EclEmma。我们将在下一个版本中修复它。

更新:PowerMock 1.6.6已经发布。它包括对JaCoCo离线检测代码的修复。所以,现在您可以使用JaCoCo maven插件和offline instrumentation

获得代码覆盖率