用Robolectric编写Android单元测试

什么是Shadow?

Shadow Objects是Robolectric在运行时插入到Android.jar包相应的类中的,它们会实际处理方法的调用,并记录相应的状态,以备在assert的时候进行查询。。请参考官方说明:http://robolectric.org/extending/

什么时候使用?

产品代码中经常会访问sqlite或者通过网络请求数据。为了保证单元测试的稳定性和运行速度,我们需要隔离这些依赖,那么我们可以使用自定义Shadow。

如何使用?

假如我们的被测代码有如下逻辑:

if (LoginUtil.isOnline()) {
    // doSomething...
} else {
    // doSomething else...
}

如果LoginUtil.isOnline()的实现很复杂,很难控制。我们就需要写一个假的实现来替换真实的实现。以方便我们测试上面的逻辑,这个假的实现就叫Shadow。

实现自定义Shadow

创建一个ShadowLoginUtil.java

@Implements(LoginUtil.class)
public class ShadowLoginUtil {
    private static boolean isOnline = false;

    @Implementation
    public static boolean isOnline() {
        return ShadowLoginUtil.isOnline;
    }

    public static boolean setOnline(boolean isOnline) {
        ShadowLoginUtil.isOnline = isOnline;
    }
}

有了这个假的实现,我们就可以在测试代码里通过调用ShadowLoginUtil.setOnline(true)来模拟登录成功的状态。

@RunWith(RobolectricTestRunner.class)
public class CustomShadowExample {
    @Test
    public void testShadow() throws Exception {
        ShadowLoginUtil.setOnline(true);
        assertTrue(LoginUtil.isOnline());

        ShadowLoginUtil.setOnline(false);
        assertFalse(LoginUtil.isOnline());
    }
}

但如果现在运行测试,是会失败的。因为Robolectric并不知道ShadowLoginUtil的存在,想要起作用,我们还要使用自定义TestRunner。

自定义TestRunner

创建MyTestRunner.java

import java.util.Arrays;
import java.util.List;

import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.bytecode.ClassInfo;
import org.robolectric.bytecode.Setup;
import org.robolectric.bytecode.ShadowMap;

public class MyTestRunner extends RobolectricTestRunner {


    public MyTestRunner(Class<?> arg0) throws InitializationError {
        super(arg0);
    }

    /**
     * List of fully qualified class names backed by custom shadows in the test harness.
     */
    private static final List<String> CUSTOM_SHADOW_TARGETS = Arrays.asList(LoginUtil.class.getName());// 需要被Shadow的类

    @Override
    protected ShadowMap createShadowMap() {
        return super.createShadowMap()
                .newBuilder()
                .addShadowClass(ShadowLoginUtil.class) // Shadow类
                .build();
    }

    @Override
    public Setup createSetup() {
        return new CustomSetup();
    }

    /**
     * Modified Robolectric {@link Setup} that instruments third party classes with custom shadows.
     */
    public class CustomSetup extends Setup {
        @Override
        public boolean shouldInstrument(ClassInfo classInfo) {
            return CUSTOM_SHADOW_TARGETS.contains(classInfo.getName())
                    || super.shouldInstrument(classInfo);
        }
    }
}

在上面的代码中,我们主要是告诉Robolectric我们想用ShadowLoginUtil来替换LoginUtil。 修改测试代码,使用自定义的TestRunner:

@RunWith(MyTestRunner.class)
public class CustomShadowExample {
   ......
}

再次运行测试,就可以顺利通过了。