模拟 WifiManager 以进行 Android 单元测试

发布于 2024-10-30 06:59:56 字数 543 浏览 0 评论 0原文

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

不幸的是,成功模拟 WifiManager 对我来说相当困难(尽管我想我可以在 MockWifiManager 中传递其构造函数空引用)。这只是我的第一个问题,因为一旦我有一个 MockWifiManager 可以使用(如果这甚至有效!),我将必须成功创建我的测试 ScanResults,它没有公共构造函数(想象它是由某个工厂创建的)。

问题: 由于它没有公共构造函数,我什至可以扩展它吗?

我这一切都错了吗?我经常被问到如何完成特定任务的问题,但实际上他们正在尝试以错误的方式解决不同的问题,也许这就是我在这里所做的?

我对 android 很陌生,所以至少可以说,必须模拟所有这些功能。

感谢您的投入!

编辑: 我在实例化 MockWifiManager 时也遇到了麻烦。 wifi 管理器的构造函数需要一个 IWifiManager 类型,但 Android SDK 中似乎不存在该类型。

I'm trying to implement some unit tests for a couple of classes that rely on WifiManager and the returned ScanResults. What I'd like to do is be able to control the ScanResults that I'm receiving in order to test a variety of different conditions.

Unfortunately it's been quite difficult for me to successfully mock up WifiManager (though I suppose I can pass its constructor null references in my MockWifiManager). This will only be my first problem as once I have a MockWifiManager to play with (if this even works!) I will have to successfully create my test ScanResults which does not have a public constructor (Imagine it's created by some factory somewhere).

Questions:
With it not having a public constructor can I even extend it?

Am I going about this all wrong? I often get asked questions about how to do a specific task but really they're trying to solve a different problem the wrong way, maybe that's what I'm doing here?

I'm very new to android so having to mock up all of this functionality has been trying to say the least.

Thanks for your inputs!

Edit:
I'm having a hell of a time instantiating a MockWifiManager as well. The constructor for wifi manager is expecting an IWifiManager a type which does not appear to exist in the Android SDK.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

远昼 2024-11-06 06:59:56

围绕 WifiManager 创建一个抽象。用这个来嘲笑你。嘲笑你不拥有的东西既困难又脆弱。如果做得正确,你应该能够切换内部结构,而且你最终会得到一个更好的可模拟 API。

为了进行测试,您可以根据自己的喜好存根/伪造经理。对于生产,您将传递一个具体实例。

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

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

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

Create an abstraction around WifiManager. Use this for your mocking. Mocking stuff you don't own is hard and brittle. If done right you should be able to switch the internals, plus you'll end up with a better mockable API.

For your testing you can stub/fake the manager to you hearts content. For production you'll pass in a concrete instance.

With regards to your point about changing your code just to make it testable that is incorrect. Firstly you should mock roles not types as discussed in the paper below. Google for more info.

Secondly creating an abstraction around third party code is a best practice as stated by the dependency inversion principle in SOLID. You should always depend on abstractions rather than concrete implementations whether you are unit testing or not.

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

幼儿园老大 2024-11-06 06:59:56

您可以尝试使用反射访问私有构造函数来创建 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 对象。

You could try to create the ScanResult instances by using reflection to access the private constructors. The code might look something like this:

        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();
        }

For other ways of testing, I most of times convert the information from instances like ScanResult and encapsulate only the information I need into my own objects. These I feed to the method doing the hard work. This makes testing easier as you can easily build these intermediate objects without relying on the real ScanResult objects.

椒妓 2024-11-06 06:59:56

我一直在努力构建 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
信息:构建扫描结果:202072179
2016 年 10 月 26 日下午 3:25:21 com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest
信息:克隆扫描结果:2004391903

因此,即使使用反射,这种方式克隆的效率也比创建实例低 10 倍。我知道这个测试并不稳健,因为优化是在编译时完成的......但是十的因素很难减轻。我也测试了 10K 次迭代,然后因子甚至是 100!仅供您参考。

PS 将 Parcel.obtain() 和 Parcel.recycle 退出循环并没有帮助

I've been struggling for a while to build ScanResult object. I have successfully used that reflection approach above.

If somebody is searching for a way to clone ScanResult object (or any other object implementing Parcelable interface) you can use this approach (I checked it right in a unit test):

@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;
    }
}

And as for performance, this naive check:

@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);
}

Showed these results:

Oct 26, 2016 3:25:19 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest
INFO: buildScanResult: 202072179
Oct 26, 2016 3:25:21 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest
INFO: cloneScanResult: 2004391903

So cloning this way is 10 times less effective than creating instance even with reflection. I know this test is not robust as optimizations are done while compiling... However factor of ten is difficult to mitigate. I did also tested 10K iterations and then the factor was even 100! Just for your information.

P.S. getting Parcel.obtain() and parcel.recycle out of loop doesn't help

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文