Android: How to run PIT Mutation Testing with Robolectric?
Asked Answered
F

1

4

How to use Robolectric and PIT for testing an Android Application?

With Robolectric, you can run Android Tests in the JVM. With PIT you can show line coverage and do mutation testing. For me, it's ok use Eclipse+Plugins, but no requirement.


This is what I have tried so far:

I have an Android Project, let's call it MyProject.

I now want to test MyProject in the JVM using Robolectric and PIT. Therefore, I created another Project called MyTest and managed to run Robolectric tests successfully, just as described in the robolectric quick start. This is what my.app.tests.MyActivityTest looks like:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
    @Test
    public void myTest() throws Exception {
        String appName = new MainActivity().getResources().getString(R.string.app_name);
        Assert.assertEquals(appName, "MyProject");
    }
}

Now the tricky part: I want to add PIT's Line Coverage and Mutation Testing to my Robolectric tests. First tried to use Pitclipse - had no luck. Pitclipse doesn't seem to support Eclipse Project Depencies yet.

So my second try is using the command line, as described in PIT quick start:

First, I made sure my tests run through successfully using Junit from command line:

java -cp <classpath> org.junit.runner.JUnitCore my.app.tests.MyActivityTest

The <classpath> contains: junit4, robolectric, MyProject class files, MyTest class files, android.jar, and other necessary android libraries.

Once this JUnit test was successful, I used the same <classpath> in my PIT call, and I execute that call in the root path of MyProject:

java -cp ../MyTest/bin:../MyTest/libs/*:bin/classes:~/android-sdk-linux/platforms/android-17/android.jar \
    org.pitest.mutationtest.MutationCoverageReport \
    --reportDir ../MyTest/pit-report \
    --targetClasses my.app.* \      # package in MyProject
    --targetTests my.app.tests.* \  # package in MyTest
    --sourceDirs src/

However, this results in the Exception I posted below. I think I need to exclude some classes using PIT's --excludedClasses parameter, but there is no hint about which class might cause the trouble. Note that MyActivityTest has no super class and no explicit constructor.

java.lang.NullPointerException
ERROR Description [testClass=my.app.tests.MyActivityTest, name=myTest(my.app.tests.MyActivityTest)] -> java.lang.NullPointerException
    at org.pitest.boot.CodeCoverageStore.visitProbes(CodeCoverageStore.java:92)
    at my.app.tests.MyActivityTest.<init>(MyActivityTest.java:22)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
    at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:195)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner.createTest(RobolectricTestRunner.java:647)
    at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:244)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:241)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner.methodBlock(RobolectricTestRunner.java:657)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:227)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:175)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.pitest.junit.adapter.CustomRunnerExecutor.run(CustomRunnerExecutor.java:42)
    at org.pitest.junit.adapter.AdaptedJUnitTestUnit.execute(AdaptedJUnitTestUnit.java:86)
    at org.pitest.coverage.execute.CoverageDecorator.execute(CoverageDecorator.java:50)
    at org.pitest.containers.UnContainer.submit(UnContainer.java:46)
    at org.pitest.Pitest$3.run(Pitest.java:148)
    at java.lang.Thread.run(Thread.java:679)
Fain answered 27/11, 2013 at 11:23 Comment(3)
I'd start off using the maven plugin - this is part of the core PIT codebase. The eclipse plugin is third party and less mature. I'm not familiar with Android development or Roboelectric - but from a quick google it sounds like it ought to work with PIT as long as normal jvm bytecode is being written to disk somewhere. If you have success (or failure) please post to the pit google user group.Chanellechaney
@Chanellechaney In a second attempt, I tried using PIT from command line. Ordinary JUnit tests (not using robolectric) are working nice with PIT. However, MyActivityTest, which is using robolectric, produces the Exception posted above. Do you have any idea on what the problem might be?Fain
I don't have experience with PIT. How is it doing mutation? Robolectric has own class loader and do bytecode manipulation on the flight. That is why there is no clear understanding how to use it with libraries like PowerMockSelfhypnosis
C
4

What looks to be happening is that two copies of pit's code coverage store class are being loaded. This is a class that tracks line coverage for each class against each test.

The classes under test are identified by an integer id that is assigned to them as they load - this id is embedded into probe calls added by bytecode manipulation that call out to the code coverage store class.

The code assumes that there will be an available entry in the store for each class id as each id is registered with the store on loading. This assumption is broken as the version of the class receiving the probe calls is different from the one against which the classes were originally registered.

This is a long way of saying that pit 0.31 and below do not look to be compatible with Roboelectric.

I'll need to take a look to see precisely what Roboelectric is doing behind the scenes to see if this can be fixed in a future release.

---- Update ---

0.32-SNAPSHOT release appears to work with Roboelectric (see comments).

Chanellechaney answered 2/12, 2013 at 20:31 Comment(7)
I know to make PIT compatible to Robolectric might be a difficult thing to do. However, both PIT and Robolectric would benefit a lot from it. Would be nice to see both working together :)Fain
This is now tracked as code.google.com/p/pitestrunner/issues/detail?id=84 . It looks as if there may be a very simple fix that could be made at the RoboElectric side. I'd prefer to look for a more generic fix in the pit code - maybe somehow move the functionality of the coverage store into a java.lang package so it will be immune from all non delegating class loader problems.Chanellechaney
@Fain The blocker with the coverage store has been removed in the latest 0.32-SNAPSHOT. Trying it against github.com/robolectric/RobolectricSample it runs without error. Mutation score looks suspiciously low though, so may be further problems.Chanellechaney
Is the snapshot already available? I tried maven-repository.com/artifact/org.pitest. Am I in the wrong place?Fain
Snapshots don't make it as far as maven central - it's available from the sonatype repos oss.sonatype.org/content/repositories/snapshots/org/pitestChanellechaney
PIT now runs through my rather large android project with 171 java files - no errors and the report is very interesting to me! Thank you so much for your efforts in this case! If I can be of help, let me know. Maybe I can contribute by writing a short tutorial about PIT+robolectric in some place?Fain
Glad to hear it's working - the sample Android project I tried it on resulted mainly in timeouts. It sounds like this was due to something other than RoboElectric. If you'd like to write up your experiences that would be great.Chanellechaney

© 2022 - 2024 — McMap. All rights reserved.