简体   繁体   English

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

[英]Spurious Test Failures using Picasso and Robolectric

I just upgraded to Robolectric 2.1.1 and integrated Picasso today. 我刚刚升级到Robolectric 2.1.1并整合毕加索。 I now have two test cases that fail randomly (one of these fragments doesn't even use Picasso). 我现在有两个随机失败的测试用例(其中一个片段甚至不使用Picasso)。 If I keep running the tests, everything usually ends up passing (might take a few tries). 如果我继续运行测试,一切通常都会通过(可能需要几次尝试)。

Test 测试

@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 Codez

I am sending in a bundle of information to the activity, which is basically an empty shell. 我正在向活动发送一大堆信息,这基本上是一个空壳。 The activity displays a Fragment via XML. 该活动通过XML显示片段。 In my Fragment, I get the data from the bundle using getActivity().getIntent().getExtras() . 在我的片段中,我使用getActivity().getIntent().getExtras()从bundle中获取数据。

Lying Warning 说谎警告

[WARN] You're instantiating an activity (com.colabug.project.singlepanel.ActivityUnderTest) directly; [WARN]您直接实例化了一个活动(com.colabug.project.singlepanel.ActivityUnderTest); consider using Robolectric.buildActivity() instead. 考虑使用Robolectric.buildActivity()代替。

Stacktrace 堆栈跟踪

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 的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>

Any ideas? 有任何想法吗? IntelliJ might be the cause since it shows up in the stacktrace. IntelliJ可能是因为它出现在堆栈跟踪中的原因。 I have used mvn clean and manually cleaned up auto generated files on the command line and rebuilt the project in IntelliJ. 我已经使用了mvn clean并在命令行上手动清理了自动生成的文件,并在IntelliJ中重建了项目。

Drew is correct. 德鲁是对的。 The NPE is occurring in a background thread. NPE发生在后台线程中。

Caused by: java.lang.NullPointerException: Bitmap config was null. 引起:java.lang.NullPointerException:位图配置为空。

Fundamentally this seems to be an issue with ShadowBitmap . 从根本上说,这似乎是ShadowBitmap一个问题。 However the solution I opted for was to create a MockPicasso class. 但是我选择的解决方案是创建一个MockPicasso类。

The stub implementation prevents the NPE. 存根实现阻止了NPE。 It also has the added benefit of preventing the unit tests from requesting bitmaps over the network in the first place. 它还具有防止单元测试首先通过网络请求位图的额外好处。

MockPicasso.java 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.
        }
    }
}

Using getLastImagePath() and getLastTargetImageView() you can test your code is requesting the correct image and loading it in the correct view without actually hitting the network. 使用getLastImagePath()getLastTargetImageView()您可以测试您的代码是否正在请求正确的图像并将其加载到正确的视图中,而无需实际访问网络。

MyActivity.java 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 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);
}

The warning isn't lying. 警告不是说谎。

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

The "new ActivityUnderTest()" qualifies as "instantiating an activity directly." “new ActivityUnderTest()”符合“直接实例化活动”的条件。

In many cases where tests fail/pass seeming-randomly, there is a threading issue in the underlying code. 在许多情况下,测试失败/通过看似随机,在底层代码中存在线程问题。 The last time this happened to me the "sometimes this fails" test was actually exposing a race condition in my queue code. 上次发生这种情况时,“有时这次失败”测试实际上是在我的队列代码中暴露了一个竞争条件。

I find that when I have the reaction "this test is stupid!" 我发现,当我得到反应“这个测试是愚蠢的!” I almost discover that the test is actually smarter than me (and nicer than me, too, as it is trying to be helpful). 我几乎发现测试实际上比我聪明(并且比我更好,因为它试图提供帮助)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM