模拟WifiManager进行Android单元测试

时间:2011-04-06 01:15:10

标签: java android unit-testing junit mocking

我正在尝试为依赖WifiManager和返回的ScanResults的几个类实现一些单元测试。我想做的是能够控制我收到的ScanResults,以便测试各种不同的条件。

不幸的是,我很难成功模拟WifiManager(虽然我想我可以在我的MockWifiManager中传递它的构造函数空引用)。这只是我的第一个问题,因为我有一个MockWifiManager可以玩(如果这甚至可以工作!)我将不得不成功创建我的测试ScanResults,它没有公共构造函数(想象一下它是由某个工厂创建的)。 / p>

问题: 如果它没有公共构造函数,我甚至可以扩展它吗?

我是否认为这一切都错了?我经常被问到如何做一个特定任务的问题,但实际上他们试图以错误的方式解决不同的问题,也许这就是我在这里做的事情?

我对android很新,所以不得不模拟所有这些功能一直试图说。

感谢您的投入!

编辑: 我也有一段时间来实例化一个MockWifiManager。 wifi管理器的构造函数期望IWifiManager是一种在Android SDK中似乎不存在的类型。

3 个答案:

答案 0 :(得分:9)

围绕WifiManager创建一个抽象。用它来嘲笑你。嘲弄你不拥有的东西是硬而脆的。如果做得对,你应该能够切换内部,再加上你最终会得到一个更好的可模拟API。

对于您的测试,您可以根据您的内容存根/伪造经理。对于生产,您将传递一个具体的实例。

关于更改代码的观点,只是为了使其可测试不正确。首先,您应该模拟角色而不是类型,如下文所述。谷歌了解更多信息。

其次,围绕第三方代码创建抽象是最佳实践,如SOLID中的依赖性反转原则所述。无论您是否进行单元测试,都应该始终依赖于抽象而不是具体的实现。

http://www.objectmentor.com/resources/articles/dip.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

答案 1 :(得分:2)

您可以尝试使用反射来访问私有构造函数来创建ScanResult实例。代码可能如下所示:

        try {
            Constructor<ScanResult> ctor = ScanResult.class.getDeclaredConstructor(null);
            ctor.setAccessible(true);
            ScanResult sr = ctor.newInstance(null);
            sr.BSSID = "foo";
            sr.SSID = "bar";
            // etc... 
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

对于其他测试方法,我大多数时候都会从ScanResult等实例转换信息,并仅将我需要的信息封装到我自己的对象中。这些是我努力工作的方法。这使得测试更容易,因为您可以轻松构建这些中间对象,而无需依赖真正的ScanResult对象。

答案 2 :(得分:0)

我一直在努力构建ScanResult对象。我已经成功地使用了上面的反射方法。

如果有人正在寻找克隆ScanResult对象(或实现Parcelable接口的任何其他对象)的方法,你可以使用这种方法(我在单元测试中检查过它):

@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public class MovingAverageQueueTests {
    @Test
    public void parcelTest() {
        Parcel parcel = Parcel.obtain();

        ScanResult sr = buildScanResult("01:02:03:04:05:06", 70);

        parcel.writeValue(sr);
        parcel.setDataPosition(0); // required after unmarshalling
        ScanResult clone = (ScanResult)parcel.readValue(ScanResult.class.getClassLoader());
        parcel.recycle();

        assertThat(clone.BSSID, is(equalTo(sr.BSSID)));
        assertThat(clone.level, is(equalTo(sr.level)));
        assertThat(clone, is(not(sameInstance(sr))));
    }

    private ScanResult buildScanResult(String mac, int level) {
        Constructor<ScanResult> ctor = null;
        ScanResult sr = null;

        try {
            ctor = ScanResult.class.getDeclaredConstructor(null);
            ctor.setAccessible(true);
            sr = ctor.newInstance(null);

            sr.BSSID = mac;
            sr.level = level;

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return sr;
    }
}

至于表现,这个天真的检查:

@Test
public void buildVsClonePerformanceTest() {
    ScanResult sr = null;

    long start = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        sr = buildScanResult("01:02:03:04:05:06", 70);
    }
    long elapsedNanos = System.nanoTime() - start;

    LOGGER.info("buildScanResult: " + elapsedNanos);

    start = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        sr = cloneScanResult(sr);
    }
    elapsedNanos = System.nanoTime() - start;

    LOGGER.info("cloneScanResult: " + elapsedNanos);
}

显示以下结果:

  

2016年10月26日下午3:25:19 com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest   信息:buildScanResult: 202072179   2016年10月26日下午3:25:21 com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest   信息:cloneScanResult: 2004391903

所以克隆这种方式的效果比使用反射创建实例要低10倍。我知道这个测试并不健全,因为在编译时进行了优化......但是10的因子很难减轻。我也测试了10K迭代,然后因子甚至是100!仅供参考。

P.S。让Parcel.obtain()和parcel.recycle脱离循环无济于事