2011-04-06 73 views
5

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

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

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

我对这一切都错了吗?我经常被问到如何完成特定任务的问题,但他们是否试图以错误的方式解决另一个问题,也许这就是我在这里做的事情?

我对android非常陌生,所以不得不模拟所有这些功能一直试图说。

感谢您的意见!

编辑: 我有一个时间实例化一个MockWifiManager以及。 WiFi管理器的构造函数期望IWFiManager类型在Android SDK中看起来不存在。

回答

8

在WifiManager上创建一个抽象。用你的嘲笑。嘲弄你不拥有的东西是困难和脆弱的。如果做得对,你应该能够切换内部结构,再加上你会得到一个更好的可嘲弄的API。

为了您的测试,您可以将经理存根/伪造成您的心中的内容。对于生产,你会传入一个具体的实例。

关于你改变你的代码的观点,只是为了让它的测试不正确。首先,你应该嘲笑角色而不是类型,如下面的论文所讨论的。 Google了解更多信息。

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

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

+0

围绕测试需求设计似乎很奇怪。包装我可能需要使用的每个系统api是否还有其他好处? – Brian 2011-04-09 23:29:15

+0

@Brian是的。首先你可以在一个地方交换WifiManager。版本x出来后会发生什么,并且API有所不同?您必须更改代码库中的很多区域。我更新了更多信息的答案。 – Finglas 2011-04-10 11:49:29

+0

啊,是的,隔离可能改变的东西。除非我用抽象来保护自己,否则我无法控制Android API。好决定! – Brian 2011-04-10 14:38:42

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对象。

+1

每当我不得不使用反射,上帝杀死一只小狗。这可能是唯一不幸的方法,感谢代码片段 – Brian 2011-04-08 13:56:09

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: 2016年10月26日下午3点25分21秒com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest 信息:cloneScanResult:

所以克隆这种方式是10倍以上,即使反射创建实例效果较差。我知道这个测试并不稳健,因为在编译时优化已经完成......但是十的因素很难减轻。我也测试了10K次迭代,然后系数甚至达到了100!只是为了您的信息。

P.S.让Parcel.obtain()和parcel.recycle走出循环没有帮助