使用Picasso和Robolectric进行虚假测试失败

时间:2013-06-13 20:53:22

标签: android unit-testing robolectric

我刚刚升级到Robolectric 2.1.1并整合毕加索。我现在有两个随机失败的测试用例(其中一个片段甚至不使用Picasso)。如果我继续运行测试,通常都会通过(可能需要几次尝试)。

测试

@Before
public void setUp() throws Exception
{
    detailActivity = Robolectric.buildActivity( ActivityUnderTest.class )
                                .withIntent( createIntent() )
                                .create()
                                .start()
                                .resume()
                                .get();

    // Note: The other test case doesn't use the fancy withIntent() doohickey
}

public static Intent createIntent()
{
    Bundle bundle = DetailFragment.createBundle( getTestData() );
    Intent intent = new Intent( new ActivityUnderTest(), Activity.class );
    intent.putExtras( bundle );
    return intent;
}

@Test
public void shouldNotBeNull() throws Exception
{
    assertNotNull( detailActivity );
}

Codez

我正在向活动发送一捆信息,这基本上是一个空壳。该活动通过XML显示片段。在我的片段中,我使用getActivity().getIntent().getExtras()从包中获取数据。

说谎警告

[WARN]您直接实例化了一个活动(com.colabug.project.singlepanel.ActivityUnderTest);考虑使用Robolectric.buildActivity()代替。

堆栈跟踪

java.lang.RuntimeException: An unexpected exception occurred
at com.squareup.picasso.Request$1.run(Request.java:114)
at org.robolectric.util.Scheduler$PostedRunnable.run(Scheduler.java:162)
at org.robolectric.util.Scheduler.runOneTask(Scheduler.java:107)
at org.robolectric.util.Scheduler.advanceTo(Scheduler.java:92)
at org.robolectric.util.Scheduler.advanceToLastPostedRunnable(Scheduler.java:68)
at org.robolectric.util.Scheduler.unPause(Scheduler.java:25)
at org.robolectric.shadows.ShadowLooper.unPause(ShadowLooper.java:219)
at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:258)
at org.robolectric.util.ActivityController.invokeWhilePaused(ActivityController.java:202)
at org.robolectric.util.ActivityController.start(ActivityController.java:144)
at com.colabug.project.singlepanel.DetailActivityTest.setUp(DetailActivityTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:241)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.NullPointerException: Bitmap config was null.
at org.robolectric.shadows.ShadowBitmap.getBytesPerPixel(ShadowBitmap.java:338)
at org.robolectric.shadows.ShadowBitmap.getRowBytes(ShadowBitmap.java:225)
at org.robolectric.shadows.ShadowBitmap.getByteCount(ShadowBitmap.java:230)
at android.graphics.Bitmap.getByteCount(Bitmap.java)
at com.squareup.picasso.Utils$BitmapHoneycombMR1.getByteCount(Utils.java:250)
at com.squareup.picasso.Utils.getBitmapBytes(Utils.java:65)
at com.squareup.picasso.Stats.processBitmap(Stats.java:64)
at com.squareup.picasso.Stats.bitmapDecoded(Stats.java:40)
at com.squareup.picasso.Picasso.loadFromType(Picasso.java:365)
at com.squareup.picasso.Picasso.resolveRequest(Picasso.java:215)
at com.squareup.picasso.Picasso.run(Picasso.java:197)
at com.squareup.picasso.Request.run(Request.java:108)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:680)
at com.squareup.picasso.Utils$PicassoThread.run(Utils.java:244)

的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.colabug</groupId>
    <artifactId>Project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>apk</packaging>
    <name>Project</name>

    <dependencies>
        <dependency>
            <groupId>com.google.android</groupId>
            <artifactId>android</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- Make sure this is below the android dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.robolectric</groupId>
            <artifactId>robolectric</artifactId>
            <version>2.1.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.squareup.picasso</groupId>
            <artifactId>picasso</artifactId>
            <version>1.0.2</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>

        <plugins>
            <plugin>
                <!-- See http://code.google.com/p/maven-android-plugin/ -->
                <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                <artifactId>maven-android-plugin</artifactId>
                <version>2.8.3</version>
                <configuration>
                    <sdk>
                        <platform>17</platform>
                    </sdk>
                </configuration>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    </build>
</project>

有什么想法吗? IntelliJ可能是因为它出现在堆栈跟踪中的原因。我使用了mvn clean并在命令行上手动清理了自动生成的文件,并在IntelliJ中重建了项目。

2 个答案:

答案 0 :(得分:3)

德鲁是对的。 NPE发生在后台线程中。

  

引起:java.lang.NullPointerException:位图配置为空。

从根本上说,这似乎是ShadowBitmap的一个问题。但是我选择的解决方案是创建一个MockPicasso类。

存根实现会阻止NPE。它还具有防止单元测试首先通过网络请求位图的附加好处。

<强> MockPicasso.java

package com.squareup.picasso;

import android.graphics.Bitmap;
import android.widget.ImageView;

public class MockPicasso extends Picasso {
    private static String lastImagePath = null;
    private static ImageView lastTargetImageView = null;

    MockPicasso() {
        super(null, null, null, Cache.NONE, null, new MockStats());
    }

    public static void init() {
        singleton = new MockPicasso();
    }

    public static String getLastImagePath() {
        return lastImagePath;
    }

    public static ImageView getLastTargetImageView() {
        return lastTargetImageView;
    }

    @Override
    public RequestBuilder load(String path) {
        lastImagePath = path;
        return new MockRequestBuilder();
    }

    class MockRequestBuilder extends RequestBuilder {
        @Override
        public void into(ImageView target) {
            lastTargetImageView = target;
        }
    }

    static class MockStats extends Stats {
        MockStats() {
            super(Cache.NONE);
        }

        @Override
        void bitmapDecoded(Bitmap bitmap) {
            // Do nothing.
        }
    }
}

使用getLastImagePath()getLastTargetImageView(),您可以测试您的代码是否正在请求正确的图片并将其加载到正确的视图中而不会实际访问网络。

<强> MyActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ImageView avatar = (ImageView) findViewById(R.id.avatar);
    String path = getIntent().getStringExtra("avatar_url");
    Picasso.with(this).load(path).into(avatar);
}

<强> MyActivityTest.java

@Test
public void shouldDisplayAvatar() throws Exception {
    MockPicasso.init();
    String imageUrl = "http://www.example.com/test.jpg";
    Intent intent = new Intent().putExtra("avatar_url", imageUrl);
    MyActivity myActivity = Robolectric.buildActivity(MyActivity.class)
            .create().get();

    myActivity.setIntent(intent);
    Robolectric.shadowOf(myActivity).callOnCreate(null);
    ImageView avatar = (ImageView) myActivity.findViewById(R.id.avatar);
    assertThat(MockPicasso.getLastImagePath()).isEqualTo(imageUrl);
    assertThat(MockPicasso.getLastTargetImageView()).isSameAs(avatar);
}

答案 1 :(得分:1)

警告不是说谎。

  

Intent intent = new Intent(new ActivityUnderTest(),Activity.class);

“new ActivityUnderTest()”有资格“直接实例化活动。”

在许多情况下,测试失败/通过似乎随机传递,底层代码中存在线程问题。上次发生这种情况时,“有时候这次失败”测试实际上是在我的队列代码中暴露了一个竞争条件。

我发现当我得到反应时“这个测试是愚蠢的!”我几乎发现测试实际上比我聪明(并且比我更好,因为它试图提供帮助)。

相关问题