qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          iOS單元測試框架Kiwi for iOS

           Kiwi是一個適用于iOS開發的行為驅動開發(BDD)庫,因其接口簡單而高效,深受開發者的歡迎,也因此,成為了許多開發新手的首選測試平臺。和大多數iOS測試框架一樣,Kiwi使用Objective-C語言編寫,因此對于iOS開發者而言,絕對稱得上是最佳測試拍檔。
            示例代碼:
          describe(@"Team", ^{
          context(@"when newly created", ^{
          it(@"should have a name", ^{
          id team = [Team team];
          [[team.name should] equal:@"Black Hawks"];
          });
          it(@"should have 11 players", ^{
          id team = [Team team];
          [[[team should] have:11] players];
          });
          });
          });

          posted @ 2014-12-23 00:21 順其自然EVO 閱讀(761) | 評論 (0)編輯 收藏

          移動應用測試框架—AppGrader(Android)

            AppGrader是來自以色列的應用測試服務商uTest推出的一款測試產品。相比其他主流移動應用測試框架,AppGrader可能并不太為開發者所熟知,但它卻能夠為眾多的Android開發者提供非常專業的意見參考。
            通過AppGrader,開發者可以將自己所開發的應用與其他同類應用就圖形、功能及其他方面進行比較,從而對應用進行改善。據悉,繼AppGrader for Android之后,uTest還將推出AppGrader for iOS。

          posted @ 2014-12-23 00:20 順其自然EVO 閱讀(499) | 評論 (0)編輯 收藏

          移動應用測試框架—Cedar(iOS)

           和Kiwi一樣,Cedar也是一款BDD風格的Objective-C測試框架。它不僅適用于iOS和OS X代碼庫,而且在其他環境下也可以使用。
            Kiwi、Specta、Expecta以及Cedar都可以通過CocoaPods添加到你的項目中。

          posted @ 2014-12-23 00:19 順其自然EVO 閱讀(472) | 評論 (0)編輯 收藏

          Appium Android Bootstrap之控件AndroidElement

          通過上一篇文章Appium Android Bootstrap源碼分析之簡介》我們對bootstrap的定義以及其在appium和uiautomator處于一個什么樣的位置有了一個初步的了解,那么按照正常的寫書的思路,下一個章節應該就要去看bootstrap是如何建立socket來獲取數據然后怎樣進行處理的了。但本人覺得這樣子做并不會太好,因為到時整篇文章會變得非常的冗長,因為你在編寫的過程中碰到不認識的類又要跳入進去進行說明分析。這里我覺得應該嘗試吸取著名的《重構》這本書的建議:一個方法的代碼不要寫得太長,不然可讀性會很差,盡量把其分解成不同的函數。那我們這里就是用類似的思想,不要嘗試在一個文章中把所有的事情都做完,而是嘗試先把關鍵的類給描述清楚,最后才去把這些類通過一個實例分析給串起來呈現給讀者,這樣大家就不會因為一個文章太長影響可讀性而放棄往下學習了。
            那么我們這里為什么先說bootstrap對控件的處理,而非剛才提到的socket相關的socket服務器的建立呢?我是這樣子看待的,大家看到本人這篇文章的時候,很有可能之前已經了解過本人針對uiautomator源碼分析那個系列的文章了,或者已經有uiautomator的相關知識,所以腦袋里會比較迫切的想知道究竟appium是怎么運用了uiautomator的,那么在appium中于這個問題最貼切的就是appium在服務器端是怎么使用了uiautomator的控件的。
            這里我們主要會分析兩個類:
            AndroidElement:代表了bootstrap持有的一個ui界面的控件的類,它擁有一個UiObject成員對象和一個代表其在下面的哈希表的鍵值的String類型成員變量id
            AndroidElementsHash:持有了一個包含所有bootstrap(也就是appium)曾經見到過的(也就是腳本代碼中findElement方法找到過的)控件的哈希表,它的key就是AndroidElement中的id,每當appium通過findElement找到一個新控件這個id就會+1,Appium的pc端和bootstrap端都會持有這個控件的id鍵值,當需要調用一個控件的方法時就需要把代表這個控件的id鍵值傳過來讓bootstrap可以從這個哈希表找到對應的控件
            1. AndroidElement和UiObject的組合關系
            從上面的描述我們可以知道,AndroidElement這個類里面擁有一個UiObject這個變量:
            public class AndroidElement {
            private final UiObject el;
            private String         id;
            ...
            }
            大家都知道UiObject其實就是UiAutomator里面代表一個控件的類,通過它就能夠對控件進行操作(當然最終還是通過UiAutomation框架). AnroidElement就是通過它來跟UiAutomator發生關系的。我們可以看到下面的AndroidElement的點擊click方法其實就是很干脆的調用了UiObject的click方法:
            public boolean click() throws UiObjectNotFoundException {
            return el.click();
            }
            當然這里除了click還有很多控件相關的操作,比如dragTo,getText,longClick等,但無一例外,都是通過UiObject來實現的,這里就不一一列舉了。
            2. 腳本的WebElement和Bootstrap的AndroidElement的映射關系
            我們在腳本上對控件的認識就是一個WebElement:
            WebElement addNote =  driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
            而在Bootstrap中一個對象就是一個AndroidElement. 那么它們是怎么映射到一起的呢?我們其實可以先看如下的代碼:
            WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
            addNote.getText();
            addNote.click();
            做的事情就是獲得Notes這個app的菜單,然后調用控件的getText來獲得‘Add note'控件的文本信息,以及通過控件的click方法來點擊該控件。那么我們看下調試信息是怎樣的:
            3. AndroidElement控件哈希表
            上一節我們說到appium pc端是通過id把WebElement和目標機器端的AndroidElement映射起來的,那么我們這一節就來看下維護AndroidElement的這個哈希表是怎么實現的。
            首先,它擁有兩個成員變量:
            private final Hashtable<String, AndroidElement> elements;
            private       Integer                           counter;
            elements :一個以AndroidElement 的id的字串類型為key,以AndroidElement的實例為value的的哈希表
            counter : 一個整型變量,有兩個作用:其一是它代表了當前已經用到的控件的數目(其實也不完全是,你在腳本中對同一個控件調用兩次findElement其實會產生兩個不同id的AndroidElement控件),其二是它代表了一個新用到的控件的id,而這個id就是上面的elements哈希表的鍵
            這個哈希表的鍵值都是從0開始的,請看它的構造函數:
            /**
            * Constructor
            */
            public AndroidElementsHash() {
            counter = 0;
            elements = new Hashtable<String, AndroidElement>();
            }
            而它在整個Bootstrap中是有且只有一個實例的,且看它的單例模式實現:
            public static AndroidElementsHash getInstance() {
            if (AndroidElementsHash.instance == null) {
            AndroidElementsHash.instance = new AndroidElementsHash();
            }
            return AndroidElementsHash.instance;
            }
            以下增加一個控件的方法addElement充分描述了為什么說counter是一個自增加的key,且是每個新發現的AndroidElement控件的id:
            public AndroidElement addElement(final UiObject element) {
            counter++;
            final String key = counter.toString();
            final AndroidElement el = new AndroidElement(key, element);
            elements.put(key, el);
            return el;
            }
            從Appium發過來的控件查找命令大方向上分兩類:
            1. 直接基于Appium Driver來查找,這種情況下appium發過來的json命令是不包含控件哈希表的鍵值信息的
            WebElement addNote = driver.findElement(By.name("Add note"));
            2. 基于父控件查找:
            WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
            以上的腳本會先嘗試找到Note1這個日記的父控件ListView,并把這個控件保存到控件哈希表,然后再根據父控件的哈希表鍵值以及子控件的選擇子找到想要的Note1:
          AndroidElementHash的這個getElement命令要做的事情就是針對這兩點來根據不同情況獲得目標控件
          /**
          * Return an elements child given the key (context id), or uses the selector
          * to get the element.
          *
          * @param sel
          * @param key
          *          Element id.
          * @return {@link AndroidElement}
          * @throws ElementNotFoundException
          */
          public AndroidElement getElement(final UiSelector sel, final String key)
          throws ElementNotFoundException {
          AndroidElement baseEl;
          baseEl = elements.get(key);
          UiObject el;
          if (baseEl == null) {
          el = new UiObject(sel);
          } else {
          try {
          el = baseEl.getChild(sel);
          } catch (final UiObjectNotFoundException e) {
          throw new ElementNotFoundException();
          }
          }
          if (el.exists()) {
          return addElement(el);
          } else {
          throw new ElementNotFoundException();
          }
          }
            如果是第1種情況就直接通過選擇子構建UiObject對象,然后通過addElement把UiObject對象轉換成AndroidElement對象保存到控件哈希表
            如果是第2種情況就先根據appium傳過來的控件哈希表鍵值獲得父控件,再通過子控件的選擇子在父控件的基礎上查找到目標UiObject控件,最后跟上面一樣把該控件通過上面的addElement把UiObject控件轉換成AndroidElement控件對象保存到控件哈希表
            4. 求證
            上面有提過,如果pc端的腳本執行對同一個控件的兩次findElement會創建兩個不同id的AndroidElement并存放到控件哈希表中,那么為什么appium的團隊沒有做一個增強,增加一個keyMap的方法(算法)和一些額外的信息來讓同一個控件使用不同的key的時候對應的還是同一個AndroidElement控件呢?畢竟這才是哈希表實用的特性之一了,不然你直接用一個Dictionary不就完事了?網上說了幾點hashtable和dictionary的差別,如多線程環境最好使用哈希表而非字典等,但在bootstrap這個控件哈希表的情況下我不是很信服這些說法,有誰清楚的還勞煩指點一二了
            這里至于為什么appium不去提供額外的key信息并且實現keyMap算法,我個人倒是認為有如下原因:
            有誰這么無聊在同一個測試方法中對同一個控件查找兩次?
            如果同一個控件運用不同的選擇子查找兩次的話,因為最終底層的UiObject的成員變量UiSelector mSelector不一樣,所以確實可以認為是不同的控件
            但以下兩個如果用同樣的UiSelector選擇子來查找控件的情況我就解析不了了,畢竟在我看來bootstrap這邊應該把它們看成是同一個對象的:
            同一個腳本不同的方法中分別對同一控件用同樣的UiSelelctor選擇子進行查找呢?
            不同腳本中呢?
            這些也許在今后深入了解中得到解決,但看家如果知道的,還望不吝賜教
            5. 小結
            最后我們對bootstrap的控件相關知識點做一個總結
            AndroidElement的一個實例代表了一個bootstrap的控件
            AndroidElement控件的成員變量UiObject el代表了uiautomator框架中的一個真實窗口控件,通過它就可以直接透過uiautomator框架對控件進行實質性操作
            pc端的WebElement元素和Bootstrap的AndroidElement控件是通過AndroidElement控件的String id進行映射關聯的
            AndroidElementHash類維護了一個以AndroidElement的id為鍵值,以AndroidElement的實例為value的全局唯一哈希表,pc端想要獲得一個控件的時候會先從這個哈希表查找,如果沒有了再創建新的AndroidElement控件并加入到該哈希表中,所以該哈希表中維護的是一個當前已經使用過的控件
          相關文章:
          Appium Android Bootstrap源碼分析之簡介

          posted @ 2014-12-23 00:18 順其自然EVO 閱讀(642) | 評論 (0)編輯 收藏

          iOS應用功能測試框架 KIF概述

            KIF的全稱是Keep It Functional,來自Square,是一款專為iOS設計的移動應用測試框架。由于KIF是使用Objective-C語言編寫的,因此,對于iOS開發者而言,用起來要更得心應手,可以稱得上是一款非常值得收藏的iOS測試利器。
            KIF最酷的地方是它是一個開源的項目,且有許多新功能還在不斷開發中。例如下一個版本將會提供截屏的功能并且能夠保存下來。這意味著當你跑完測試之后,可以在你空閑時通過截圖來查看整個過程中的關鍵點。難道這不是比鼠標移動上去并用肉眼觀察KIF點擊和拖動整個過程好上千倍萬倍么?KIF變得越來越好了,所以學習如何使用它,對于自己來說是一個很好的投資。
            由于KIF測試用例是繼承了OCUnit,并使用了標準的Xcode5測試框架,你可以使用持續集成來跑這個測試。當你在忙著別的事情的時候,就擁有了一個能夠像人的手指一樣準點觸控的機器人去測試你的應用程序。太棒了!

          posted @ 2014-12-23 00:17 順其自然EVO 閱讀(421) | 評論 (0)編輯 收藏

          移動應用測試框架—Calabash Android 簡介

          什么是 Calabash
            Calabash 是一個自動化測試框架,它可以測試 Android 和 iOS 原生應用和混合應用。
            它有:
            calabash-android
            calabash-ios
            主頁: http://calabash.sh
            Calabash-android介紹
            Calabash-android 是支持 android 的 UI 自動化測試框架,PC 端使用了 cucumber 框架,通過 http 和 json 與模擬器和真機上安裝的測試 apk 通信,測試 apk 調用 Robotium 的方法來進行 UI 自動化測試,支持 webview 操作。
            Calabash-android 架構圖
            Features —— 這里的 feature 就是 cucumber 的 feature,用來描述 user stories 。
            Step Definitions —— Calabash Android 事先已經定義了一些通用的 step。你可以根據自己的需求,定義更加復雜的步驟。
            Your app —— 測試之前,你不必對你的應用修改。(這里其實是有問題,后面我們會說到。)
            Instrumentation Test Server —— 這是一個應用,在運行測試的時候會被安裝到設備中去。 這個應用是基于 Android SDK 里的 ActivityInstrumentationTestCase2。它是 Calabash Android 框架的一部分。Robotium 就集成在這個應用里。
            Calabash-android 環境搭建
            ruby 環境
            rvm
            rbenv
            RubyInstaller.org for windows
            Android 開發環境
            JAVA
            Android SDK
            Ant
            指定 JAVA 環境變量, Android SDK 環境變量(ANDROID_HOME), Ant 加入到 PATH 中去。
            安裝 Calabash-android
            gem install calabash-android
            sudo gem install calabash-android # 如果權限不夠用這個。
            如有疑問,請參考: https://github.com/calabash/calabash-android/blob/master/documentation/installation.md
            創建 calabash-android 的骨架
            calabash-android gen
            會生成如下的目錄結構:
            ?  calabash  tree
            .
            features
            |_support
            | |_app_installation_hooks.rb
            | |_app_life_cycle_hooks.rb
            | |_env.rb
            |_step_definitions
            | |_calabash_steps.rb
            |_my_first.feature
          寫測試用例
            像一般的 cucumber 測試一樣,我們只要在 feature 文件里添加測試用例即可。比如我們測試 ContactManager.apk (android sdk sample 里面的, Appium 也用這個 apk)。
            我們想實現,
            打開這個應用
            點擊 Add Contact 按鈕
            添加 Contact Name 為 hello
            添加 Contact Phone 為 13817861875
            添加 Contact Email 為 hengwen@hotmail.com
            保存
           所以我們的 feature 應該是這樣的:
            Feature: Login feature  Scenario: As a valid user I can log into my app    When I press "Add Contact"
            Then I see "Target Account"
            Then I enter "hello" into input field number 1    Then I enter "13817861875" into input field number 2    Then I enter "hengwen@hotmail.com" into input field number 3    When I press "Save"
            Then I wait for 1 second    Then I toggle checkbox number 1    Then I see "hello"
            這里 input field number 就針對了 ContactAdder Activity 中輸入框。我現在這樣寫其實不太友好,比較好的方式是進行再次封裝,對 DSL 撰寫者透明。比如:
          When I enter "hello" as "Contact Name"
          step_definition
          When (/^I enter "([^\"]*)" as "([^\"]*)"$/) do | text, target |
          index = case target
          when "Contact Name": 1
          ...
          end
          steps %{
          Then I enter #{text} into input field number #{index}
          }end
            這樣 feature 可讀性會強一點。
            運行 feature
            在運行之前,我們對 apk 還是得處理下,否則會遇到一些問題。
            App did not start (RuntimeError)
            因為calabash-android的client和test server需要通信,所以要在 AndroidManifest.xml 中添加權限:
            <uses-permission android:name="android.permission.INTERNET" />
            ContacterManager 代碼本身的問題
            由于 ContacerManager 運行時候,需要你一定要有一個賬戶,如果沒有賬戶 Save 的時候會出錯。為了便于運行,我們要修改下。
            源代碼地址在 $ANDROID_HOME/samples/android-19/legacy/ContactManager,大家自己去找。
            需要修改 com.example.android.contactmanager.ContactAdder 類里面的 createContactEntry 方法,我們需要對 mSelectedAccount 進行判斷, 修改地方如下:
          // Prepare contact creation request
          //
          // Note: We use RawContacts because this data must be associated with a particular account.
          //       The system will aggregate this with any other data for this contact and create a
          //       coresponding entry in the ContactsContract.Contacts provider for us.
          ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
          if(mSelectedAccount != null ) {
          ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
          .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
          .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
          .build());
          } else {
          ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
          .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
          .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
          .build());
          }....
          if (mSelectedAccount != null) {
          // Ask the Contact provider to create a new contact
          Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
          mSelectedAccount.getType() + ")");
          } else {
          Log.i(TAG,"No selected account");
          }
            代碼修改好之后,導出 apk 文件。
            運行很簡單:
            calabash-android run <apk>
            如果遇到簽名問題,請用: calabash-android resign apk。
            可以看看我運行的情況:
          ?  calabash  calabash-android run ContactManager.apk
          Feature: Login feature
          Scenario: As a valid user I can log into my app                # features/my_first.feature:33135 KB/s (556639 bytes in 0.173s)3315 KB/s (26234 bytes in 0.007s)
          When I press "Add Contact"                                   # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
          Then I see "Target Account"                                  # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:5
          Then I enter "hello" into input field number 1               # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
          Then I enter "13817861875" into input field number 2         # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
          Then I enter "hengwen@hotmail.com" into input field number 3 # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
          When I press "Save"                                          # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
          Then I wait for 1 second                                     # calabash-android-0.4.21/lib/calabash-android/steps/progress_steps.rb:18
          Then I toggle checkbox number 1                              # calabash-android-0.4.21/lib/calabash-android/steps/check_box_steps.rb:1
          Then I see "hello"                                           # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:51 scenario (1 passed)9 steps (9 passed)0m28.304s
          All pass!
            大家看到 gif 是 failed,是因為在模擬器上運行的。而上面全部通過的是我在海信手機上運行的。環境不一樣,略有差異。
            總結
            本文是對 calabash-android 的一個簡單介紹,做的是拋磚引玉的活。移動測試框架并非 Appium 一家,TesterHome 希望其他框架的話題也能熱火起來。watch and learn!

          posted @ 2014-12-23 00:15 順其自然EVO 閱讀(841) | 評論 (0)編輯 收藏

          Appium Server源碼分析之作為Bootstrap客戶端

               摘要: Appium Server擁有兩個主要的功能:  它是個http服務器,它專門接收從客戶端通過基于http的REST協議發送過來的命令  他是bootstrap客戶端:它接收到客戶端的命令后,需要想辦法把這些命令發送給目標安卓機器的bootstrap來驅動uiatuomator來做事情  通過上一篇文章《Appium Server 源碼分析之啟動運行Express http服務器》我們分...  閱讀全文

          posted @ 2014-12-23 00:14 順其自然EVO 閱讀(3508) | 評論 (0)編輯 收藏

          移植MonkeyRunner的圖片對比功能實現-Appium篇

           如果你的目標測試app有很多imageview組成的話,這個時候monkeyrunner的截圖比較功能就體現出來了。而其他幾個流行的框架如Robotium,UIAutomator以及Appium都提供了截圖,但少了兩個功能:
            獲取子圖
            圖片比較
            既然Google開發的MonkeyRunner能盛行這么久,且它體功能的結果驗證功能只有截屏比較,那么必然有它的道理,有它存在的價值,所以我們很有必要在需要的情況下把它相應的功能給移植到其他框架上面上來。
            經過本人前面文章描述的幾個框架的源碼的研究(robotium還沒有做),大家可以知道MonkeyRunner是跑在PC端的,只有在需要發送相應的命令事件時才會驅動目標機器的monkey或者shell等。比如獲取圖片是從目標機器的buffer設備得到,但是比較圖片和獲取子圖是從客戶PC端做的。
            這里Appium工作的方式非常的類似,因為它也是在客戶端跑,但需要注入事件發送命令時還是通過目標機器段的bootstrap來驅動uiatuomator來完成的,所以要把MonkeyRunner的獲取子圖已經圖片比較的功能移植過來是非常容易的事情。
            但UiAutomator就是另外一回事了,因為它完全是在目標機器那邊跑的,所以你的代碼必須要android那邊支持,所以本人在移植到UiAutomator上面就碰到了問題,這里先給出Appium 上面的移植,以方便大家的使用,至于UiAutomator和Robotium的,今后本人會酌情考慮是否提供給大家。
            還有就是這個移植過來的代碼沒有經過優化的,比如失敗是否保存圖片以待今后查看等。大家可以基于這個基礎實現滿足自己要求的功能
            1. 移植代碼
            移植代碼放在一個Util.java了工具類中:
          public static boolean sameAs(BufferedImage myImage,BufferedImage otherImage, double percent)
          {
          //BufferedImage otherImage = other.getBufferedImage();
          //BufferedImage myImage = getBufferedImage();
          if (otherImage.getWidth() != myImage.getWidth()) {
          return false;
          }
          if (otherImage.getHeight() != myImage.getHeight()) {
          return false;
          }
          int[] otherPixel = new int[1];
          int[] myPixel = new int[1];
          int width = myImage.getWidth();
          int height = myImage.getHeight();
          int numDiffPixels = 0;
          for (int y = 0; y < height; y++) {
          for (int x = 0; x < width; x++) {
          if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
          numDiffPixels++;
          }
          }
          }
          double numberPixels = height * width;
          double diffPercent = numDiffPixels / numberPixels;
          return percent <= 1.0D - diffPercent;
          }
          public static BufferedImage getSubImage(BufferedImage image,int x, int y, int w, int h)
          {
          return image.getSubimage(x, y, w, h);
          }
          public static BufferedImage getImageFromFile(File f) {
          BufferedImage img = null;
          try {
          img = ImageIO.read(f);
          } catch (IOException e) {
          //if failed, then copy it to local path for later check:TBD
          //FileUtils.copyFile(f, new File(p1));
          e.printStackTrace();
          System.exit(1);
          }
          return img;
          }
          這里就不多描述了,基本上就是基于MonkeyRunner做輕微的修改,所以叫做移植。而UiAutomator就可能需要大改動,要重現實現了。
           2. 客戶端調用代碼舉例
          packagesample.demo.AppiumDemo;
          importstaticorg.junit.Assert.*;
          importjava.awt.image.BufferedImage;
          importjava.io.File;
          importjava.io.IOException;
          importjava.net.URL;
          importjavax.imageio.ImageIO;
          importlibs.Util;
          importio.appium.java_client.android.AndroidDriver;
          importorg.apache.commons.io.FileUtils;
          importorg.junit.After;
          importorg.junit.Before;
          importorg.junit.Test;
          importorg.openqa.selenium.By;
          importorg.openqa.selenium.OutputType;
          importorg.openqa.selenium.WebElement;
          importorg.openqa.selenium.remote.DesiredCapabilities;
          publicclassCompareScreenShots{
          privateAndroidDriverdriver;
          @Before
          publicvoidsetUp()throwsException{
          DesiredCapabilitiescap=newDesiredCapabilities();
          cap.setCapability("deviceName","Android");
          cap.setCapability("appPackage","com.example.android.notepad");
          cap.setCapability("appActivity",".NotesList");
          driver=newAndroidDriver(newURL("http://127.0.0.1:4723/wd/hub"),cap);
          }
          @After
          publicvoidtearDown()throwsException{
          driver.quit();
          }
          @Test
          publicvoidcompareScreenAndSubScreen()throwsInterruptedException,IOException{
          Thread.sleep(2000);
          WebElementel=driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
          el.click();
          Thread.sleep(1000);
          Stringp1="C:/1";
          Stringp2="C:/2";
          Filef2=newFile(p2);
          Filef1=driver.getScreenshotAs(OutputType.FILE);
          FileUtils.copyFile(f1,newFile(p1));
          BufferedImageimg1=Util.getImageFromFile(f1);
          f2=driver.getScreenshotAs(OutputType.FILE);
          FileUtils.copyFile(f2,newFile(p2));
          BufferedImageimg2=Util.getImageFromFile(f2);
          Booleansame=Util.sameAs(img1,img2,0.9);
          assertTrue(same);
          BufferedImagesubImg1=Util.getSubImage(img1,6,39,474,38);
          BufferedImagesubImg2=Util.getSubImage(img1,6,39,474,38);
          same=Util.sameAs(subImg1,subImg2,1);
          Filef3=newFile("c:/sub-1.png");
          ImageIO.write(subImg1,"PNG",f3);
          Filef4=newFile("c:/sub-2.png");
          ImageIO.write(subImg1,"PNG",f4);
          }
          }
            也不多解析了,沒有什么特別的東西。
            大家用得上的就支持下就好了...

          posted @ 2014-12-23 00:10 順其自然EVO 閱讀(1029) | 評論 (0)編輯 收藏

          Android單元測試框架 Robolectric

           Android開發者們注意了,這款測試框架一定會讓你們興奮不已,因為它是一款已基本上擺脫了模擬器測試的老套路的速率單元測試框架Robolectric可以解壓Android SDK,還能直接對應用進行測試,從而幫你輕而易舉地解決所遇到的任何問題。
            Robolectric 是一款Android單元測試框架,示例代碼:
          @RunWith(RobolectricTestRunner.class)
          public class MyActivityTest {
          @Test
          public void clickingButton_shouldChangeResultsViewText() throws Exception {
          Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();
          Button pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
          TextView results = (TextView) activity.findViewById(R.id.results_text_view);
          pressMeButton.performClick();
          String resultsText = results.getText().toString();
          assertThat(resultsText, equalTo("Testing Android Rocks!"));
          }
          }

          posted @ 2014-12-23 00:08 順其自然EVO 閱讀(467) | 評論 (0)編輯 收藏

          JUnit中的測試套件和參數化測試

          JUnit4.x的測試運行器
            JUnit單元測試提供了默認的測試運行器,它的測試方法都是由它負責執行的
            我們也可以定制自己的運行器,所有的運行器都繼承自org.junit.runner.Runner
            還可以使用org.junit.runer.RunWith注解 為每個測試類指定使用具體的運行器
            一般情況下,默認測試運行器可以應對絕大多數的單元測試要求
            當使用JUnit提供的一些高級特性,或者針對特殊需求定制JUnit測試方式時
            顯式的聲明測試運行就必不可少了
            JUnit4.x測試套件的創建步驟
            ① 創建一個空類作為測試套件的入口
            ② 使用org.junit.runner.RunWith 和org.junit.runners.Suite.SuiteClasses注解 修飾該空類
            ③ 將org.junit.runners.Suite 作為參數傳入RunWith注解,即使用套件運行器執行此類
            ④ 將需要放入此測試套件的測試類組成數組,作為SuiteClasses注解的參數
            ⑤ 保證這個空類使用public 修飾,而且存在公開的不帶有任何參數的構造函數
            下面是JUnit4.x中創建測試套件類的示例代碼
          package com.jadyer.junit4;
          import org.junit.runner.RunWith;
          import org.junit.runners.Suite;
          import org.junit.runners.Suite.SuiteClasses;
          /**
          * JUnit4.x測試套件的舉例
          * @see 下面的CalculatorTest.class和ParameterTest.class均為我們自己編寫的JUnit4單元測試類
          */
          @RunWith(Suite.class)
          @SuiteClasses({CalculatorTest.class, ParameterTest.class})
          public class TestAll {}
            下面是JUnit3.8中創建測試套件類的示例代碼
          package com.jadyer.junit3;
          import junit.framework.Test;
          import junit.framework.TestCase;
          import junit.framework.TestSuite;
          /**
          * JUnit3.8中批量運行所有的測試類。。直接在該類上Run As JUnit Test即可
          * @see 這里就用到了設計模式中典型的組合模式,即將不同的東西組合起來
          * @see 組合之后的東西,即可以包含本身,又可以包含組成它的某一部分
          * @see TestSuite本身是由TestCase來組成的,那么TestSuite里面就可以包含TestCase
          * @see 同時TestSuite里面還可以繼續包含TestSuite,形成一種遞歸的關系
          * @see 這里就體現出來了,所以這是一種非常非常好的設計模式,一種好的策略
          */
          public class TestAll extends TestCase {
          //方法名固定的,必須為public static Test suite()
          public static Test suite() {
          //TestSuite類實現了Test接口
          TestSuite suite = new TestSuite();
          //這里傳遞的是測試類的Class對象。該方法還可以接收TestSuite類型對象
          suite.addTestSuite(CalculatorTest.class);
          suite.addTestSuite(MyStackTest.class);
          return suite;
          }
          }


          JUnit4.X的參數化測試
            為保證單元測試的嚴謹性,通常會模擬不同的測試數據來測試方法的處理能力
            為此我們需要編寫大量的單元測試的方法,可是這些測試方法都是大同小異的
            它們的代碼結構都是相同的,不同的僅僅是測試數據和期望值
            這時可以使用JUnit4的參數化測試,提取測試方法中相同代碼 ,提高代碼重用度
            而JUnit3.8對于此類問題,并沒有很好的解決方法,JUnit4.x彌補了JUnit3.8的不足
            參數化測試的要點
            ① 準備使用參數化測試的測試類必須由org.junit.runners.Parameterized 運行器修飾
            ② 準備數據。數據的準備需要在一個方法中進行,該方法需要滿足的要求如下
            1) 該方法必須由org.junit.runners.Parameterized.Parameters注解 修飾
            2) 該方法必須為返回值是java.util.Collection 類型的public  static方法
            3) 該方法沒有參數 ,方法名可隨意 。并且該方法是在該類實例化之前執行的
            ③ 為測試類聲明幾個變量 ,分別用于存放期望值和測試所用的數據
            ④ 為測試類聲明一個帶有參數的公共構造函數 ,并在其中為③ 中聲明的變量賦值
            ⑤ 編寫測試方法,使用定義的變量作為參數進行測試
            參數化測試的缺點
            一般來說,在一個類里面只執行一個測試方法。因為所準備的數據是無法共用的
            這就要求,所要測試的方法是大數據量的方法,所以才有必要寫一個參數化測試
            而在實際開發中,參數化測試用到的并不是特別多
            下面是JUnit4.x中參數化測試的示例代碼
            首先是Calculator.java
          package com.jadyer.junit4;
          /**
          * 數學計算-->加法
          */
          public class Calculator {
          public int add(int a, int b) {
          return a + b;
          }
          }
            然后是JUnit4.x的參數化測試類ParameterTest.java
          package com.jadyer.junit4;
          import static org.junit.Assert.assertEquals; //靜態導入
          import java.util.Arrays;
          import java.util.Collection;
          import org.junit.Test;
          import org.junit.runner.RunWith;
          import org.junit.runners.Parameterized;
          import org.junit.runners.Parameterized.Parameters;
          import com.jadyer.junit4.Calculator;
          /**
          * JUnit4的參數化測試
          */
          @RunWith(Parameterized.class)
          public class ParameterTest {
          private int expected;
          private int input11;
          private int input22;
          public ParameterTest(int expected, int input11, int input22){
          this.expected = expected;
          this.input11 = input11;
          this.input22 = input22;
          }
          @Parameters
          public static Collection prepareData(){
          //該二維數組的類型必須是Object類型的
          //該二維數組中的數據是為測試Calculator中的add()方法而準備的
          //該二維數組中的每一個元素中的數據都對應著構造方法ParameterTest()中的參數的位置
          //所以依據構造方法的參數位置判斷,該二維數組中的第一個元素里面的第一個數據等于后兩個數據的和
          //有關這種具體的使用規則,請參考JUnit4的API文檔中的org.junit.runners.Parameterized類的說明
          Object[][] object = {{3,1,2}, {0,0,0}, {-4,-1,-3}, {6,-3,9}};
          return Arrays.asList(object);
          }
          @Test
          public void testAdd(){
          Calculator cal = new Calculator();
          assertEquals(expected, cal.add(input11, input22));
          }
          }
          /********************【該測試的執行流程】************************************************************************/
          //1..首先會執行prepareData()方法,將準備好的數據作為一個Collection返回
          //2..接下來根據準備好的數據調用構造方法。Collection中有幾個元素,該構造方法就會被調用幾次
          //   我們這里Collection中有4個元素,所以ParameterTest()構造方法會被調用4次,于是會產生4個該測試類的對象
          //   對于每一個測試類的對象,都會去執行testAdd()方法
          //   而Collection中的數據是由JUnit傳給ParameterTest(int expected, int input11, int input22)構造方法的
          //   于是testAdd()用到的三個私有參數,就被ParameterTest()構造方法設置好值了,而它們三個的值就來自于Collection
          /************************************************************************************************************/

          posted @ 2014-12-23 00:07 順其自然EVO 閱讀(687) | 評論 (0)編輯 收藏

          僅列出標題
          共394頁: 上一頁 1 2 3 4 5 6 7 8 9 下一頁 Last 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 博野县| 连州市| 芮城县| 茂名市| 来凤县| 麦盖提县| 门源| 东城区| 卢湾区| 栾川县| 湘阴县| 荣昌县| 炎陵县| 安陆市| 历史| 岚皋县| 同心县| 玛曲县| 屯门区| 青浦区| 阳原县| 沙坪坝区| 颍上县| 五寨县| 文昌市| 伊金霍洛旗| 漯河市| 博客| 颍上县| 拉萨市| 西林县| 屯留县| 新津县| 镇坪县| 淳安县| 固始县| 甘洛县| 礼泉县| 嘉黎县| 吐鲁番市| 湘西|