世界級的安卓測試開發流!

OneAPM官方技術部落格發表於2016-02-24

在「世界級的安卓測試開發流 — 第一部分」,作者開始了安卓測試開發流的討論。我們探討了一個軟體工程師開始編寫測試,到發現測試開發中的相關問題的不斷變化。 最後,得到了以下結論:

  • 測試自動化對於軟體開發的成功是至關重要的
  • 可測試性程式碼對編寫某些特定型別的測試是必須的
  • 有些開發者在不確定測試內容和測試方法的情況下,就開始編寫測試
  • 測試的質量和可靠度通常達不到我們的期望
  • 一個測試開發流對於定義測試內容和方法是必要的

因此,任何應用程式中測試的關鍵部分是:

  • 業務邏輯的測試要獨立於框架或庫
  • 測試伺服器端的API整合
  • 從使用者的角度,使用黑盒測試驗收標準

在本文中,我們將探討涉及這幾個部分的幾種測試方案,以確保一個穩固的測試開發流。

業務邏輯的測試要獨立於框架或庫:

首先,檢查業務邏輯是不是真的實現預定的產品需求,是必不可少的。我們需要將想要測試的程式碼隔離出來,然後模擬出不同的初始場景,以配置某些元件在執行時的行為。然後,我們選擇想要執行的程式碼部分進行測試。之後,我們需要在執行完測試物件後檢查軟體的狀態是否正確。

這個測試方案的關鍵在於依賴反轉原則。通過編寫只依賴於抽象的程式碼,我們就可以把軟體分離成不同的層級。為了獲得依賴的例項,我們只需要向某個物件發出請求。 或者,一旦例項生成,我們也能獲得之。 我們的軟體有很多部分需要建立程式碼來獲得合作者的例項。這時,我們會使用測試替身來模擬初始場景,或編寫不同的行為程式來設計我們的測試。通過使用測試替身,我們能同時模擬產品程式碼的行為和狀態。 同時,它幫助我們選擇測試的範圍,也即需要測試的程式碼量。沒有依賴反轉,所有的類需要獨自獲取自己的依賴。結果會導致類實現和依賴實現相耦合,這樣就沒有辦法借用測試替身來切割產品程式碼的執行流程。

通常,在構造時傳遞類依賴是使用依賴反轉的最有效機制。這個機制對採用測試替身已經足夠。在構造時傳遞類依賴將幫助我們建立相應的測試替身來替換依賴的例項。要謹記,使用服務定位器或依賴反轉框架將有助於減少在應用依賴反轉時所需要的樣板, 雖然這並不是強制的。

我們將用一個具體例子(該測試和筆者幾個月前開始開發的 Android GameBoy 模擬器有關)來演示如何測試我們的業務需求。

以下測試是關於 GameBoy 記憶體管理單元(MMU)和 GameBoy BIOS 執行單元的。 我們將檢查產品需求(硬體模擬)是否正確實現。

``` public class MMUTest {
private static final int MMU_SIZE = 65536; private static final int ANY_ADDRESS = 11; private static final byte ANY_BYTE_VALUE = 0x11;

@Test public void shouldInitializeMMUFullOfZeros() { MMU mmu = givenAMMU();

assertMMUIsFullOfZeros(mmu);

}

@Test public void shouldFillMMUWithZerosOnReset() { MMU mmu = givenAMMU();

mmu.writeByte(ANY_ADDRESS, ANY_BYTE_VALUE);
mmu.reset();

assertMMUIsFullOfZeros(mmu);   

}

@Test public void shouldWriteBigBytesValuesAndRecoverThemAsOneWord() { MMU mmu = givenAMMU();

mmu.writeByte(ANY_ADDRESS, (byte) 0xFA);
mmu.writeByte(ANY_ADDRESS +1, (byte) 0xFB);

assertEquals(0xFBFA, mmu.readWord(ANY_ADDRESS));

} } ```

前三個測試是檢查 GameBoy 的 MMU 實現是否正確。 成功的關鍵是總在測試執行完成後,檢查 MMU 的狀態是否正確。 所有測試都會檢查 MMU 是否正確初始化。如果重置後MMU 是整潔的,寫入2個位元組後讀出的是一個字元,那麼最終讀取就是正確的。為了測試模擬器軟體的這個部分,我們將測試範圍縮小為一個類。

``` public class GameBoyBIOSExecutionTest {

@Test public void shouldIndicateTheBIOSHasBeenLoadedUnlockingTheRomMapping() { GameBoy gameBoy = givenAGameBoy();

tickUntilBIOSLoaded(gameBoy);

assertEquals(1, mmu.readByte(UNLOCK_ROM_ADDRESS) & 0xFF);

}

@Test public void shouldPutTheNintendoLogoIntoMemoryDuringTheBIOSThirdStage() { GameBoy gameBoy = givenAGameBoy();

tickUntilThirdStageFinished(gameBoy);

assertNintendoLogoIsInVRAM();

}

private GameBoy givenAGameBoy() { z80 = new GBZ80(); mmu = new MMU(); gpu = new GPU(mmu); GameLoader gameLoader = new GameLoader(new FakeGameReader()); GameBoy gameBoy = new Gameboy(z80, mmu, gpu, gameLoader); return gameboy; }

} ```

在這兩個測試中,我們檢查 BIOS 是否在不同階段都正確執行。BIOS執行結束後,在具體記憶體位置的一個位元組必須被初始化為一個具體的值。然後,在第三階段的最後,任天堂的logo必須已經載入到 VRAM。由於全套的BIOS執行是任何模擬器開發的關鍵部分,我們決定採用更大的測試範圍。這個測試用例的測試物件是 CPU,部分 CPU 指令集(僅涉及 BIOS執行的指令)和 MMU。 要檢查執行的狀態是否正確,我們必須對 MMU的狀態使用斷言(assert)。要想顯著的提升測試質量,一個關鍵就是檢查軟體執行後的最終狀態,同時避免驗證和其他元件之間的互動。因為即使和其他元件之間的互動都是正確的,最終狀態依然可能是錯的。還要明確,這些測試的某些部分同樣可以獨立進行,如 CPU指令。

這些測試的另一個主要亮點是使用測試替身來模擬和 Android SDK 相關的部分程式碼。在執行 BIOS 前,GameBoy 遊戲必須載入進 GameBoy 的 MMU。然而,在測試期間,沒有 Android SDK 可用,因而就不得不將其替換,轉而從測試環境中載入 GameBoy的 rom。我們使用依賴反轉原則不僅用於隱藏實現細節,或者定義邊界,還用測試替身 FakeGameReader 來替代 AndroidGameReader 的產品程式碼,這意味著完全不依賴框架和庫進行程式碼測試。通過這樣做,我們建立了隔離的測試環境,同時調整了測試範圍。

範圍:

調整測試的範圍是非常重要的。 開始編寫測試前,我們應當牢記測試範圍可以幫助識別程式碼錯誤(取決於測試的規模)。縮小測試規模能帶給我們更豐富的錯誤反饋,而放大規模的測試則不能提供錯誤位置的精確資訊。測試的粒度則應當和測試範圍相當。

基礎設施:

編寫這些測試的基礎設施相當明瞭。 我們必須在依賴反轉原則下編寫可測試程式碼,並結合使用一個測試框架和一個模擬庫。 模擬庫用來生成模擬場景中需要的測試替身,或者用於替代部分產品程式碼的測試替身。請注意,這些框架和庫不是強制使用的,但是非常推薦。

結果:

這個方案的結果很有趣。 當遵循依賴反轉原則時,我們能獨立於框架和庫測試我們產品程式碼中的業務邏輯。通過可重複的,易於編寫和設計的測試,我們可以建立隔離的測試環境。此外,我們能夠方便選擇要測試的產品程式碼數量,並使用測試替身代替這部分程式碼,來模擬行為和不同的場景。

一旦我們能夠測試產品需求是否正確實現,我們必須繼續測試開發流。接下來要測試的,就是在前一階段使用測試替身替換的外部元件整合後是否執行正確。我們將在下一篇博文中討論,敬請期待!

原文地址:http://blog.karumi.com/world-class-testing-development-pipeline-for-android-part-2/

OneAPM Mobile Insight ,監控網路請求及網路錯誤,提升使用者留存。訪問 OneAPM 官方網站感受更多應用效能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格 本文轉自 OneAPM 官方部落格

相關文章