单元测试汇总
轉自:https://blog.csdn.net/u012933335/rss/list
| [原]Android單測調研篇 |
1. 為什么做單測單測的好處減少bug 快速定位bug 提高代碼質量 減少調試時間 放心重構不得不寫單測的原因在成為大牛的路上,單測是必備技能 單測可以給你信心 保住面子難處難于堅持,在快速迭代開發過程中,可供寫單測的時間過少 擴展TDD(Test Drive Develop):測試驅動開發,是一種非常高效的開發方式 2. 測試框架2.1 概述junit4.12 (單測框架)mockito1.9.5(mock工具)robolectric3.1.2 (模擬Android虛擬機)2.2 junit4.12Junit測試是程序員測試,即所謂白盒測試,因為程序員知道被測試的軟件如何(How)完成功能和完成什么樣(What)的功能。Junit是一套框架,繼承TestCase類,就可以用Junit進行自動測試了。 示例代碼: @Before: 執行單測之前的初始化操作。 @After:單測完成后收尾工作。 @Beforepublic void setUp() throws Exception {}@Afterpublic void tearDown() throws Exception {}assume: 用于判斷測試用例的入參是否有業務含義的工具,如果入參不符合預期時會拋出 assumptionViolatedException、assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoException @RunWith(Theories.class) public class AssumeTest {@DataPointspublic static String[] names = {"LiLei", "HanMeiMei"};@DataPointspublic static int[] ages = {10, -2, 12};@Theorypublic void printAge(String name, int age){Assume.assumeTrue(age > 0);System.out.println(String.format("%s's Name is %s.", name, age));} }assert?:用于常用的測試結果驗證 AssertTrue、AssertFalse:結果的true、false。 AssertThat:使用Matcher做自定義的校驗。 AssertEquals、AssertNotEquals:判斷兩個對象是否相等。 AssertNull、AssertNotNull:判斷對象是否為空。 AssertSame:判斷兩個對象是否為同一個,不同于equals這里是使用“==”判斷。 AssertArrayEquals:判斷兩個數組是否相等。 @Testpublic void sum() throws Exception {assertEquals(mCalculator.sum(3, 4), 7);}verify?: 主要用于驗證方法是否執行 @Testpublic void testVerify() {List mockedList = mock(List.class);mockedList.add("one");mockedList.clear();mockedList.add("3");// verificationverify(mockedList).add("one");verify(mockedList).clear();}其他高級用法: @Test(timeout = 1000): 限時操作,若超過制定時間,強制停止 @Test(expected = ArithmeticException.class): 預測拋出指定異常 2.3 mockito1.9.5創建mock對象不能對final,Anonymous ,primitive類進行mock。? when… thenRetrun; when… thenThrow doNothing().doRetrun(); doNothing.doThrow() anyInt、anyString、anyMap…..(參數匹配器) @Testpublic void argumentMatcherTest2(){Map2.4 robolectric3.1.2實現一套JVM能運行的Android代碼,從而做到脫離Android環境進行測試1.測試跳轉 /*** Activity跳轉測試*/@Testpublic void testStartActivity() {//按鈕點擊后跳轉到下一個ActivityforwardBtn.performClick();Intent expectedIntent = new Intent(sampleActivity, LoginActivity.class);Intent actualIntent = ShadowApplication.getInstance().getNextStartedActivity();assertEquals(expectedIntent.getComponent(), actualIntent.getComponent());}2.模擬activity sampleActivity = Robolectric.setupActivity(SampleActivity.class);更多場景還需探索。。。 與espresso的對比Google 官方提供的一個易于測試 Android UI 的開源框架 , 于2013年10月推出它的 released 版本 , 目前最新版本已更新到2.x . 并且在AndroidStudio 2.2 預覽版中已經默認集成該測試庫 。 ViewMatchers - 在當前View層級去匹配指定的View . ViewActions - 執行Views的某些行為,如點擊事件 . ViewAssertions - 檢查Views的某些狀態,如是否顯示 . @RunWith(AndroidJUnit4.class) public class LoginUITest {@Rulepublic ActivityTestRule rule=new ActivityTestRule(LogingActivity.class,true);@Testpublic void login(){//loginonView(withId(R.id.userName)).perform(typeText("Jack"),closeSoftKeyboard());onView(withId(R.id.password)).perform(typeText("1234"),closeSoftKeyboard());onView(withText("登錄")).perform(click());//verifyonView(withId(R.id.content)).check(matches(isDisplayed()));} }espresso更偏向于自動化測試,集成后執行單元測試需要跑在Android手機上,其有個高級功能,根據你的點擊軌跡,自動生成自動測試代碼。 3. 覆蓋率jacoco:Android Studio自帶的生成單元測試覆蓋率報告的工具。行覆蓋率:度量被測程序的每行代碼是否被執行,判斷標準行中是否至少有一個指令被執行。 類覆蓋率:度量計算class類文件是否被執行。 分支覆蓋率:度量if和switch語句的分支覆蓋情況,計算一個方法里面的 總分支數,確定執行和不執行的 分支數量。 方法覆蓋率:度量被測程序的方法執行情況,是否執行取決于方法中是否有至少一個指令被執行。 指令覆蓋:計數單元是單個java二進制代碼指令,指令覆蓋率提供了代碼是否被執行的信息,度量完全 獨立源碼格式。 圈復雜度:在(線性)組合中,計算在一個方法里面所有可能路徑的最小數目,缺失的復雜度同樣表示測試案例沒有完全覆蓋到這個模塊。 參考自:http://blog.csdn.net/tmq1225/article/details/52221187集成配置: apply plugin: 'jacoco' android {buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}debug{testCoverageEnabled true}} } jacoco {toolVersion = "0.7.5.201505241946" }jacoco覆蓋率報告分為兩種: 1. 只生成java層代碼覆蓋率報告2. 在運行app期間執行的覆蓋率報告 task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") {println("=========jacocoTestReport start");group = "Reporting"description = "Generate Jacoco coverage reports"classDirectories = fileTree(dir: "${project.buildDir}/intermediates/classes/debug",excludes: ['**/R.class','**/R$*.class','**/*$ViewInjector*.*','**/BuildConfig.*','**/Manifest*.*'])println("path==========>>" + "${project.buildDir}/intermediates/classes/debug")def coverageSourceDirs = "${project.projectDir}/src/main/java"println("coverageSourceDirs==========>>" + coverageSourceDirs)additionalSourceDirs = files(coverageSourceDirs)sourceDirectories = files(coverageSourceDirs)executionData = fileTree(dir: project.projectDir, includes:['**/*.exec', '**/*.ec'])reports {xml.enabled = truehtml.enabled = true} }task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){group = "Reporting"description = "Generate Jacoco coverage reports after running tests."reports{xml.enabled = truehtml.enabled = truecsv.enabled = false}classDirectories = fileTree(dir : "$buildDir/intermediates/classes/debug",excludes : ['**/*Test.class','**/R.class','**/R$*.class','**/BuildConfig.*','**/Manifest*.*'])def coverageSourceDirs = ['src/main/java']additionalSourceDirs = files(coverageSourceDirs)sourceDirectories = files(coverageSourceDirs)additionalClassDirs = files(coverageSourceDirs)executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec") }結果展示總結單元測試的一些原則 |
| ? |
| [原]Android單測踩坑篇 |
| 在開始寫單測之前,已經調研了很久Android單測的框架以及demo,正好換了一個新的項目組,就從頭開始將單測框架應用到開發過程中,后續也方便可以使用TDD。 調研的框架:junit,mockito, roboletric,espresso,jacoco(覆蓋率報告) 具體場景:網絡請求,todomvp的單測方式,UI測試等。 理想永遠是美好的,擼起袖子開始干的時候,就會發現還有很多崎嶇需要去踏平。 首先,我放棄了espresso(雖然是google官方出的測試框架),主要原因是espresso依賴真機或模擬器。我相信大部分應用應該都有模擬器判斷,在模擬器環境下禁止啟動app,為了寫單測,還得改代碼,很難受;另外多數開發團隊應該都是持續集成,跑espresso,還需要有一臺手機常年插在構建機上,很麻煩。這并不是說espresso毫無用處,espresso對于Android框架支持非常完善,基本上所有UI的測試都可以實現。另外,espresso還有一個非常強大的功能,適合測試人員手工測試:錄制測試過程,自動生成測試代碼。比方說:你進入app后,按照正確測試流程點擊一遍后,保存下來,下一次開發人員有了些許改動,你只需要運行一次之前自動生成的測試代碼,應用便會按照之前的流程進行測試,如出現異常,則測試不通過!非常強大(據團隊的IOS工程師說,ios也有一樣的測試框架,個人覺得測試人員可以考慮一下這個)。 所以最初我采用的Android單測框架就是:junit + mockito + roboletric + jacoco。 junit和mockito就不要多講了,都是java單元測試最基本的框架。在說roboletric之前,得先說一下powerMock,mockito無法mock static方法,而powermock則解決了這個問題。所以powermock和mockito配合使用,基本就可以覆蓋絕大部分情況。不過由于powermock和mockito是兩個團隊實現的,經常出現版本不兼容的情況,建議直接使用powermock內部引用的mockito,這樣就不會沖突了。? 貼上地址: https://github.com/robolectric/robolectric/wiki/Using-PowerMock在使用框架時,注意對應版本。頁面中的第一句話: NOTE: PowerMock integration is broken in Robolectric 3.1 and 3.2, but fixed in 3.3.我使用的完整配置如下: apply plugin: 'com.android.library' apply plugin: 'android-apt' apply plugin: 'com.jakewharton.butterknife' apply plugin: 'jacoco'android {buildTypes {debug {testCoverageEnabled true } } } /** * 生成jacoco測試報告 */ task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") { println("=========jacocoTestReport start"); group = "Reporting" description = "Generate Jacoco coverage reports" classDirectories = fileTree( dir: "${project.buildDir}/intermediates/classes/debug", excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*'] ) println("path==========>>" + "${project.buildDir}/intermediates/classes/debug") def coverageSourceDirs = "${project.projectDir}/src/main/java" println("coverageSourceDirs==========>>" + coverageSourceDirs) additionalSourceDirs = files(coverageSourceDirs) sourceDirectories = files(coverageSourceDirs) println("executionData==========>>" + "$buildDir/jacoco/testDebugUnitTest.exec") executionData = files("$buildDir/jacoco/testDebugUnitTest.exec") reports { xml.enabled = true html.enabled = true } } jacoco { toolVersion = "0.7.1.201405082137" } dependencies { compile files('libs/fastjson-1.1.51.android.jar') compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.cmbchina.ccd.pluto:CMBCore:1.0.0-SNAPSHOT@aar' compile 'com.jakewharton:butterknife:8.4.0' apt 'com.jakewharton:butterknife-compiler:8.4.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.6.1' testCompile 'org.powermock:powermock-api-mockito:1.6.6' testCompile 'org.powermock:powermock-module-junit4:1.6.6' testCompile 'org.powermock:powermock-module-junit4-rule:1.6.6' testCompile 'org.powermock:powermock-classloading-xstream:1.6.6' }原本使用powermock的版本是1.7.X,發現使用的過程中各種報錯,還是使用了官方的1.6.6版本,不知道這兩個團隊什么時候兼容能做的很完善。 附上使用BaseTest: @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class , sdk = 21) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*", "sun.security.*", "javax.net.*"}) @PrepareForTest({CPSNetUtils.class, Common.class}) public abstract class CPSBaseTest { @Rule public PowerMockRule rule = new PowerMockRule(); @Before public void setUp() { // 將log日志打印到控制臺 ShadowLog.stream = System.out; initLog(); //mockito 初始化 MockitoAnnotations.initMocks(this); //mock靜態方法所在類 PowerMockito.mockStatic(CPSNetUtils.class, Common.class); Common.application = getApplication(); new FoundationBuildConfig().init(); initNetMock(); mockCommon(); } }@Config(constants = BuildConfig.class , sdk = 21):原本想使用23的sdk版本,會有不兼容問題。 @PowerMockIgnore({“org.mockito.“, “org.robolectric.“, “android.“, “org.json.“, “sun.security.“, “javax.net.“}):根據我的理解:powermock類加載器忽略以上類的加載。 @PrepareForTest({CPSNetUtils.class, Common.class}):想mock static的方法,必須加上此注解。 基本配置如上,若上述配置都沒問題了,就可以真正開始寫單測了。目前Android端的框架使用的是google推薦的mvp框架,優缺點,網上有很多文章,就不在贅述。github地址如下: https://github.com/googlesamples/android-architecture/tree/todo-mvp/不可否認的一點,使用mvp框架后單測實現會簡單很多,極大程度減少了對view的測試。貼上一段業務測試代碼: public class FeedbackPresenterTest extends CPSBaseTest { @InjectMocks private FeedbackPresenter mPresenter; @Mock private FeedbackContract.View mView; @Before public void setUp() { super.setUp(); mPresenter = new FeedbackPresenter(); mPresenter.attachView(mView); } @Test public void feedbackFailTest() { when(mView.getFeedback()).thenReturn(""); when(mView.getContact()).thenReturn("15012341234"); mPresenter.uploadFeedback(); verify(mView).showToastInBottom("反饋信息不能為空!"); } @Test @Config(shadows = {ShadowCommon.class}) public void feedbackSuccessTest() { when(mView.getFeedback()).thenReturn("閃退!"); when(mView.getContact()).thenReturn("15012341234"); mPresenter.uploadFeedback(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } verify(mView).showToastInBottom("操作成功"); } }細心的讀者會發現在feedbackSuccessTest測試方法中,我開了個線程睡了半秒鐘,這時候就要說到網絡請求的單元測試了。網絡交互是客戶端最頻繁的場景,而網絡的不穩定,會導致客戶端出現很多難以預知的情況,崩潰,閃退都有可能發生。所以對于網絡請求的單測是重中之重。我使用的網絡庫是okhttp,而okhttp有一個很強大的功能:Interceptor。interceptor可以攔截網絡請求,可以處理完后繼續發送,也可以直接直接返回。廢話不多說,上代碼: public class MockInterceptor implements CMBHttpInterceptor { @Override public Response intercept(Chain chain) throws IOException { Response response; String json = ""; if (chain.request().url().toString().equals(FoundationHostConst.LOGIN)) { // login json = "{\"respMsg\": \"用戶名或密碼錯誤[CD1105]\",\"respCode\": \"1001\"}"; } else if (chain.request().url().toString().equals(FoundationHostConst.REGISTER)) { //register json = "{\n" + " \"data\": {\n" + " \"sessionId\": \"c742f1a3915a445d997735413ca12a78\",\n" + " \"userId\": \"7ac3960080e94be38c79ac83808b579a\",\n" + " \"channel\": \"MOB\"\n" + " },\n" + " \"respMsg\": \"操作成功\",\n" + " \"respCode\": \"1000\"\n" + " }"; } else if (chain.request().url().toString().equals(FoundationHostConst.FEEDBACK)) { //feedback json = "{\n" + " \"respMsg\": \"操作成功\",\n" + " \"respCode\": \"1000\"\n" + " }"; } response = setResponse(chain, json); return response; } /** * 設置指定返回報文 * * @param chain * @param response * @return */ private Response setResponse(Chain chain, String response) { return new Response.Builder() .code(200) .addHeader("Content-Type", "multipart/form-data") .body(ResponseBody.create(MediaType.parse("multipart/form-data"), response)) .message(response) .request(chain.request()) .protocol(Protocol.HTTP_2) .build(); } }當然也可以模擬異常返回,404什么的都可以。另外okhttp使用的是建造者模式,客戶端網絡請求OkHttpClient都是一致的,故可以使用類似代碼直接mock返回: CMBHttpClient.Builder builder CMBHttpUtils.getDefaultClientBuilder().addInterceptor(new CPSInterceptor()).addInterceptor(new CMBLogInterceptor()).addInterceptor(new MockInterceptor()); PowerMockito.when(CPSNetUtils.getCPSDefaultBuilder()).thenReturn(builder);單測寫完,總得看到點數據報告吧。這時候就需要覆蓋率報告了。Android studio自帶了jacoco 插件生成覆蓋率報告。? 行覆蓋率:度量被測程序的每行代碼是否被執行,判斷標準行中是否至少有一個指令被執行。 類覆蓋率:度量計算class類文件是否被執行。 分支覆蓋率:度量if和switch語句的分支覆蓋情況,計算一個方法里面的總分支數,確定執行和不執行的分支數量。 方法覆蓋率:度量被測程序的方法執行情況,是否執行取決于方法中是否有至少一個指令被執行。 指令覆蓋:計數單元是單個java二進制代碼指令,指令覆蓋率提供了代碼是否被執行的信息,度量完全 獨立源碼格式。 圈復雜度:在(線性)組合中,計算在一個方法里面所有可能路徑的最小數目,缺失的復雜度同樣表示測試案例沒有完全覆蓋到這個模塊。 參考自:http://blog.csdn.net/tmq1225/article/details/52221187jacoco的覆蓋率報告分為兩種: 1. 只生成java層代碼覆蓋率報告2. 在運行app期間執行的覆蓋率報告方法也不盡相同。生成java層代碼的任務在前面代碼中已經貼出。 生成運行app期間執行的覆蓋率報告代碼如下: //task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){group = "Reporting"description = "Generate Jacoco coverage reports after running tests."reports{xml.enabled = true html.enabled = true csv.enabled = false } classDirectories = fileTree( dir : "$buildDir/intermediates/classes/debug", excludes : [ '**/*Test.class', '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*' ] ) def coverageSourceDirs = ['src/main/java'] additionalSourceDirs = files(coverageSourceDirs) sourceDirectories = files(coverageSourceDirs) additionalClassDirs = files(coverageSourceDirs) executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec") }以上代碼并未測試過,點擊執行測試后,會啟動app,并在會生成.ec文件,通過解析.ec文件, 可以生成覆蓋率報告。因為前面放棄esspreso時就說過放棄真機測試,所以此方法也未使用。? 另外,中間遇到一個大坑,被坑了很久。就是jacoco和roboletric也有版本不兼容的問題。。。使用最新的jacoco的版本,發現生成的覆蓋率報告始終為0 在stackoverflow中找了查了很久,最后在一個帖子里看到,在jacoco 0.7.3以上的版本使用roboletric就始終為0,嘗試了多個版本,發現 0.7.1.201405082137 是OK的。千萬不要隨便升級版本,否則會出現異想不到的問題。。 好了,下面就看一下覆蓋率報告: 后續會接入jenkins,等踩完坑,再補一篇文章。 作者:u012933335 發表于 2018/02/07 18:32:37?原文鏈接?https://blog.csdn.net/u012933335/article/details/79283201 閱讀:86 |
轉載于:https://www.cnblogs.com/weizhxa/p/9365595.html
總結
- 上一篇: js获取当前Frame在父页面中的id
- 下一篇: Python基础(三):字符串和元组常用