1. 程式人生 > 實用技巧 >Android單元測試

Android單元測試

實很多開發者都知道單元測量,也能寫一些簡單的單元測試,但是就我工作以來,很少,基本沒有看到專案中有編寫單元測試的。因為編寫額外的程式碼,麻煩,加上不熟悉,就更加不想寫了。我以前也是這種想法,但是最近的接觸,然後覺得,做單元測試還是很有必要的。

舉例

  • 網路請求

    比如測試一個功能,而這個功能會進行網路請求,當出現問題時,我們得拿到網路請求返回的資料,這樣才知道是後端問題,還是前端邏輯問題。

    而進入這個功能需要進行好幾步操作,如果需要更改什麼配置,還需要重新安裝apk,想想過程都複雜,而且重新安裝apk可以一個耗時的過程。

    這時候,我們就可以用單元測試,可以在不重新安裝apk,不用去幾番操作就可以拿到網路請求的結果。下面會實戰舉例。

  • api測試

    這種情況,用單元測試最快捷的,只需要在“test”目錄下編寫程式碼,在本機執行即可,比起安裝apk,然後去點點點方便很多。

缺點

缺點當然就是要編寫額外的測試程式碼,如果業務邏輯有改動,測試程式碼也得相應改動,存在後期維護,還有一點點的學習成本。不過總得來說,還是利大於弊的。

單元測試

單元測試可以直接在業務程式碼的module下編寫程式碼,也可以專門建一個單元測試module。

加入我們,群。642830685,領取最新軟體測試大廠面試資料和Python自動化、介面、框架搭建學習資料!同行一起交流,技術大牛解惑答疑

業務module下做單元測試

我們在新建module的時候,Android Studio會在資源目錄src下生成“androidTest”和“test”兩個目錄,並且有生成一個簡單的單元測試檔案。單元測試需要的相應依賴也會配置好。你只需要在檔案中編寫測試程式碼即可。

  • androidTest目錄:

    這些測試在硬體裝置或模擬器上執行。這些測試有權使用 Instrumentation API,可讓您獲取某些資訊(例如您要測試的應用的 Context,並且可讓您通過測試程式碼來控制受測應用。在編寫整合和功能介面測試來自動執行使用者互動時,或者當您的測試具有模擬物件無法滿足的 Android 依賴項時,可以使用這些測試。

  • test目錄:

    這些測試在計算機的本地 Java 虛擬機器 (JVM) 上執行。如果您的測試沒有 Android 框架依賴項,或者您可以模擬 Android 框架依賴項,使用這些測試可以最大限度地縮短執行時間。

單獨的測試module

我們可以像建立lib庫那樣,給需要測試的工程建立一個用於單元測試的module。

  • 第一步

建立一個Android Library module

  • 第二步

將“apply plugin: 'com.android.library'”改成“apply plugin: 'com.android.test'”

  • 第三步

新增測試所用的依賴庫時,和其它module一樣用“implementation”等,而不是“androidTestImplementation”等

  • 第四步

指定需要被測試的module,在“AndroidManifest.xml”下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.pds.testapp" >
    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="com.pds.blog"/>
</manifest>

 

在build.gradle中指定需要被測試的module: targetProjectPath ':app'。

android:targetPackage 指定需要被測試module的包名,targetProjectPath這是module在工程中的路徑。

測試相關配置

具體欄位的意思,可以參考官網。

android {
    compileSdkVersion app.compileSdkVersion
    defaultConfig {
        minSdkVersion 26
        targetSdkVersion app.targetSdkVersion
        testApplicationId "com.pds.test.${project.name}"
        testHandleProfiling true
        testFunctionalTest true
        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
        // 配置需要被測試的工程,和settings.gradle名字一致
        targetProjectPath ':app'
        javaCompileOptions {annotationProcessorOptions {includeCompileClasspath = true}}}
    testOptions {
        reportDir "$rootDir/test_app/test-reports"
        resultsDir "$rootDir/test_app/test-results"
        // 要僅為本地單元測試指定選項,請配置 testOptions {} 中的 unitTests {} 程式碼塊。
        unitTests {
            // 如果您的測試依賴於資源 預設情況下,Android Studio 3.4 及更高版本提供編譯版本的資源。
            includeAndroidResources = true
            all {
                jvmArgs '-XX:MaxPermSize=256m'
                if (it.name == 'test_app') {systemProperty 'debug', 'true'}
            }
        }
    }
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

 

基礎依賴

dependencies {
    implementation 'androidx.test:rules:1.2.0'
    implementation 'androidx.test:runner:1.2.0'
    implementation 'org.hamcrest:hamcrest-core:1.3'
    implementation 'androidx.test.ext:junit:1.1.1'
    implementation 'androidx.test.ext:truth:1.2.0'
    implementation 'com.google.truth:truth:0.42'
}

實戰

  • 啟動Activity
@Test
public void launchMarqueeTextPage() {
      Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
      Intent intent = new Intent(context, GlideTestActivity.class);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      ActivityTestRule<GlideTestActivity> activityTestRule = new ActivityTestRule<>(GlideTestActivity.class, true, false);
      activityTestRule.launchActivity(intent);
      // 讓介面不自動退出
      try {
          CountDownLatch countdown = new CountDownLatch(1);
          countdown.await();
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
}

 在測試module裡,如果啟動繼承於AppCompatActivity的Activity會報錯,目前沒有找到解決辦法,可以正常啟動繼承於Activity的Activity。

  • 網路請求測試

比如在業務module,定義了一套網路請求的api,那麼我們可以直接引用業務module裡面寫好的網路api,來發起網路請求。

 

@Test
public void getPhoneNumber() {
    Disposable observable
            = ApiManager
            .getUserApi()
            .getPhoneNumber("")
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .compose(SchedulersCompat.<BaseEntity<DataEntity>>applyIoSchedulers())
            .map(new HttpResultFunc<DataEntity>())
            .subscribe(new Consumer<DataEntity>() {
                @Override
                public void accept(DataEntity data) throws Exception {

                }
            }, new ErrorConsumer() {
                @Override
                public void accept(Throwable throwable) {
                    super.accept(throwable);
                }
            });
}
  •  自定義Rule 

  

@Rule
 public TipsRule tipsRule = new TipsRule();

  

public class TipsRule implements TestRule {
  @Override
  public Statement apply(final Statement base, final Description description) {
      return new Statement() {
          // evaluate前執行方法相當於@Before
          @Override
          public void evaluate() throws Throwable {
              // 獲取測試方法的名字
              String methodName = description.getMethodName();
              System.out.println("-------"+ methodName + "------>測試開始!");
              // 執行的測試方法
              base.evaluate();
              // evaluate後執行方法相當於@After
              System.out.println("-------"+ methodName + "------>測試結束!");
          }
      };
  }
}
  • 自定義Matcher

@Test
public void testAssertThatMatcher(){assertThat("19508460000",new   MobilePhoneMatcher());}

 

public class MobilePhoneMatcher extends BaseMatcher<String> {

    /**
     * 進行斷言判定,返回true則斷言成功,否則斷言失敗
     */
    @Override
    public boolean matches(Object item) {
        if (null == item) return false;
        Pattern pattern = Pattern.compile("(1|861)(3|5|7|8)\\d{9}$*");
        Matcher matcher = pattern.matcher((String) item);
        return matcher.find();
    }

    /**
     * 給期待斷言成功的物件增加描述
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("預計此字串是手機號碼!");
    }
    /**
     * 給斷言失敗的物件增加描述
     */
    @Override
    public void describeMismatch(Object item, Description description) {
        description.appendText(item.toString() + "不是手機號碼!");
    }
}

  

monkeyrunner

monkeyrunner 工具提供了一個 API,用於編寫可從 Android 程式碼外部控制 Android 裝置或模擬器的程式。使用 monkeyrunner,您可以編寫一個 Python 程式,以便安裝 Android 應用或測試軟體包,執行它,向其傳送按鍵,擷取其介面的螢幕截圖,並將螢幕截圖儲存到工作站中。monkeyrunner 工具主要用於在功能/框架級測試應用和裝置以及執行單元測試套件,但您也可以自由地將其用於其他目的。參考:monkeyrunner

用python編寫測試指令碼,然後用monkeyrunner工具執行。

monkeyrunner可執行檔案存在於sdk/tools/bin目錄下,編寫好的python指令碼用monkeyrunner命令執行,例如: monkeyrunner monkey.py。單獨執行python檔案是不行的,沒法匯入python中用到的Java庫。

 希望本文對你有所幫助,點個讚唄!