2011-04-06 47 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走出循環沒有幫助