iOS 猴子測試

所謂“猴子測試”,就是自動化UI測試龟再,模擬在屏幕上隨便亂點尼变,來達(dá)到自動化測試的目的嫌术,節(jié)省了人力。

測試非常簡單割按,本文用到了Github上的 UI AutoMonkey 來集成的适荣,地址:https://github.com/jonathanpenn/ui-auto-monkey

步驟如下:

1.選擇Instrument中的Automation院领,清空里面的js代碼。
2.把 UI AutoMonkey 中的 UIAutoMonkey.js 文件里面的400多行js代碼拷貝汪诉,粘貼到Automation里面扒寄。(或者直接拷貝下面的js代碼)
3.點擊左上角的紅色按鈕運行拟烫,模擬器就開始工作了硕淑,可以看到屏幕自動出現(xiàn)快速亂點的效果嘉赎。

測試效果展示:


Untitled7.gif

備注:
測試包括了:點擊公条、滾動迂曲、清掃路捧、捏合、橫豎屏等队寇。

附j(luò)s測試代碼:

"use strict";

function UIAutoMonkey() {
        
    this.config = {
        //run either by minutesToRun or numberOfEvents. Only one of these can set. (To use minutes you can use config.numberOfEvents = 0)
        //minutesToRun = 60 * 8; //sample to run for 8 hours.
        //checkTimeEvery = 60; //how often to check (in events) if minutesToRun has is used. 
        numberOfEvents: 1000,
        delayBetweenEvents: 0.05,    // In seconds
        
        /**
        * Sometimes the monkey can fall into UI Holes from which it it is hard to escape. The monkey may then spend an inordinate
        * amount of time in such holes, neglecting other parts of the application.
        *
        * For example, if a parent Window P has a large image
        * and clicking on the image opens a child window C from which one exits by tapping a small X on the top right, then until that small X is
        * tapped we will remain in C. conditionHandlers offer the developer the option to periodically recognize that we are in C and press the X.
        *
        * See buttonHandler.js for a specialized conditionHandler useful when a top level button can be used to escape from a UI hole.
        *
        * conditionHandlers are objects that respond to the following methods:
        *  isTrue(target, eventNumber): returns True if the condition is true given target and event number eventNumber.
        *  checkEvery(): How many events should pass before we check.
        *  handle(target, mainWindow) handle the condition.
        *  isExclusive() if true then if this condition's handler is invoked then processing subsequent conditions is skipped for this particular event. This
        *    is usually set to true as it allows the condition to exit a UI hole and at that point there may be no point executing other conditions
        *  logStats() log statics using UIALogger;
        * condition handlers must have the following property
        *  statsHandleInvokedCount - the count of the number of times we were invoked
        */

        conditionHandlers: [],
        
        /**
        * Unfortunately if the application is not responsive "ANR", the monkey may not notice and continue to fire events not realizing that
        * the application is stuck. When run via continuous integration users may not notice that "successful" monkey runs in fact were in an 
        * ANR state.
        *
        * To deal with this the monkey supports ANR detection. Using an anrFingerprint function it periodically takes a fingerprint and if these
        * are identical for a specified interval then an ANR exception is thrown.
        *
        *
        */
        anrSettings: {
            //fingerprintFunction defaults to false which will disable ANR fingerprinting. Otherwise set to a function that will return
            //a string. One useful idiom using tuneup.js is
            //#import tuneup.js
            //config.anrSettings.fingerprintFunction = function() {return logVisibleElementTreeJSON(false)};
            fingerprintFunction: false,
            eventsBeforeANRDeclared: 1500, //throw exception if the fingerprint hasn't changed within this number of events
            eventsBetweenSnapshots: 150, //how often (in events) to take a snapshot using the fingerprintFunction 
            debug: false //if true extra logging is made            
        },

        // If the following line is uncommented, then screenshots are taken
        // every "n" seconds.
        //screenshotInterval: 5,

        // Events are triggered based on the relative weights here. The event
        // with this highest number gets triggered the most.
        //
        // If you want to add your own "events", check out the event method
        // definitions below.
        eventWeights: {
            tap: 500,
            drag: 1,
            flick: 1,
            orientation: 1,
            clickVolumeUp: 1,
            clickVolumeDown: 1,
            lock: 1,
            pinchClose: 10,
            pinchOpen: 10,
            shake: 1
        },

        // Probability that touch events will have these different properties
        touchProbability: {
            multipleTaps: 0.05,
            multipleTouches: 0.05,
            longPress: 0.05
        }
        

        // Uncomment the following to restrict events to a rectangular area of
        // the screen
        /*
        frame: {
            origin: {x: 0, y: 0},
            size: {width: 100, height: 50}
        }
        */

    };

    // Dismiss alerts 
    UIATarget.onAlert = function onAlert(alert) {
        var title = alert.name();
        UIALogger.logMessage('On Alert: ' + title);
        return true;
    }
}

// --- --- --- ---
// Event Methods
//
// Any event probability in the hash above corresponds to a related event
// method below. So, a "tap" probability will trigger a "tap" method.
//
// If you want to add your own events, just add a probability to the hash
// above and then add a corresponding method below. Boom!
//
// Each event method can call any other method on this UIAutoMonkey object.
// All the methods at the end are helpers at your disposal and feel free to
// add your own.

UIAutoMonkey.prototype.allEvents = {
    tap: function() {
        this.target().tapWithOptions(
            { x: this.randomX(), y: this.randomY() },
            {
                tapCount: this.randomTapCount(),
                touchCount: this.randomTouchCount(),
                duration: this.randomTapDuration()
            }
        );
    },

    drag: function() {
        this.target().dragFromToForDuration(
            { x: this.randomX(), y: this.randomY() },
            { x: this.randomX(), y: this.randomY() },
            0.5
        );
    },

    flick: function() {
        this.target().flickFromTo(
            { x: this.randomX(), y: this.randomY() },
            { x: this.randomX(), y: this.randomY() }
        );
    },

    orientation: function() {
        var orientations = [
            UIA_DEVICE_ORIENTATION_PORTRAIT,
            UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN,
            UIA_DEVICE_ORIENTATION_LANDSCAPELEFT,
            UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT
        ];

        var i = Math.floor(Math.random() * 10) % orientations.length;
        var newOrientation = orientations[i];
        this.target().setDeviceOrientation(newOrientation);
        this.delay(0.9);
    },

    clickVolumeUp: function() {
        this.target().clickVolumeUp();
    },

    clickVolumeDown: function() {
        this.target().clickVolumeDown();
    },

    lock: function() {
        this.target().lockForDuration(Math.random() * 3);
    },

    pinchClose: function () {
        this.target().pinchCloseFromToForDuration(
            { x: this.randomX(), y: this.randomY() },
            { x: this.randomX(), y: this.randomY() },
            0.5
        );
    },

    pinchOpen: function () {
        this.target().pinchOpenFromToForDuration(
            { x: this.randomX(), y: this.randomY() },
            { x: this.randomX(), y: this.randomY() },
            0.5
        );
    },

    shake: function() {
        this.target().shake();
    }
};

// --- --- --- ---
// Helper methods
//
UIAutoMonkey.prototype.RELEASE_THE_MONKEY = function() {
    // Called at the bottom of this script to, you know...
    //
    // RELEASE THE MONKEY!
    if (this.config.minutesToRun && this.config.numberOfEvents) {
        throw "invalid configuration. You cannot define both minutesToRun and numberOfEvents"
    }
    var conditionHandlers = this.config.conditionHandlers || []; //For legacy configs, if not present default to empty.
    var useConditionHandlers = conditionHandlers.length > 0;
    var checkTime = false;
    var localNumberOfEvents = this.config.numberOfEvents; //we may modify so we want to leave config untouched
    if (this.config.minutesToRun) {
        checkTime = true;
        localNumberOfEvents = 2000000000;
        var startTime = new Date().getTime();
        var checkTimeEvery = this.config.checkTimeEvery || 60; //number of events to pass before we check the time
    }
    //setup anr parameters as needed
    var anrFingerprintFunction = this.config.anrSettings ? this.config.anrSettings.fingerprintFunction : false; //handle legacy settings missing this
    if (anrFingerprintFunction) {
        this.anrSnapshot = "Initial snapshot-nothing should match this!!";
        this.anrSnapshotTakenAtIndex = -1;      
        var anrEventsBetweenSnapshots = this.config.anrSettings.eventsBetweenSnapshots || 300;
        var anrDebug = this.config.anrSettings.debug;
        this.anrMaxElapsedCount = -1;
    } 
    
    var targetBundleId = this.target().frontMostApp().bundleID();
    for (var i = 0; i < localNumberOfEvents; i++) {
        if (checkTime && (i % checkTimeEvery == 0)) { //check the time if needed
            var currTime = new Date().getTime();
            var elapsedMinutes = (currTime-startTime) / 60000;
            if (elapsedMinutes >= this.config.minutesToRun) {
                UIALogger.logDebug("Ending monkey after " + elapsedMinutes + " minutes run time.");
                break;
            } else {
                UIALogger.logDebug(this.config.minutesToRun - elapsedMinutes + " minutes left to run.")
            }
        }

        var currentBundleId = this.target().frontMostApp().bundleID();
        if (currentBundleId !== targetBundleId) {
            UIALogger.logDebug("Ending monkey because it went outside of the tested app ('" + currentBundleId + "')");
            break;
        }

        this.triggerRandomEvent();
        if (anrFingerprintFunction && (i % anrEventsBetweenSnapshots == 0)) this.anrCheck(i, anrFingerprintFunction, anrDebug);
        if (this.config.screenshotInterval) this.takeScreenShotIfItIsTime();
        if (useConditionHandlers) this.processConditionHandlers(conditionHandlers, i+1, this.target());
        this.delay();
    }
    // publish stats if warranted
    if (anrFingerprintFunction) {
        UIALogger.logDebug("ANR Statistics");
        UIALogger.logDebug("ANR max event count for identical fingerprint snapshots :: events before ANR declared: " + this.anrMaxElapsedCount + " :: " + this.config.anrSettings.eventsBeforeANRDeclared);
    }
    if (useConditionHandlers) {
        UIALogger.logDebug("ConditionHandler Statistics")
        conditionHandlers.forEach(function(aHandler) {aHandler.logStats()});
        conditionHandlers.sort(function(aHandler, bHandler) {return aHandler.statsHandleInvokedCount - bHandler.statsHandleInvokedCount});
        UIALogger.logDebug("sorted by HandleInvokedCount");
        conditionHandlers.forEach(function(aHandler) {UIALogger.logDebug(aHandler + ": " + aHandler.statsHandleInvokedCount)});
    }
};


UIAutoMonkey.prototype.anrCheck = function(i, fingerprintFunction, debugFlag){

    var newSnapshot = fingerprintFunction();
    if (newSnapshot != this.anrSnapshot) {
        //all is good, we're moving along
        if (debugFlag) UIALogger.logDebug("UIAutoMonkey:anrCheck(): snapshot != for event " + i);
        this.anrSnapshot = newSnapshot;
        this.anrSnapshotTakenAtIndex = i;
    } 
    else {
        //have a match
        //for how many counts?
        var elapsedCount = i - this.anrSnapshotTakenAtIndex;
        this.anrMaxElapsedCount = Math.max(this.anrMaxElapsedCount, elapsedCount);
        UIALogger.logDebug("UIAutoMonkey:anrCheck(): snapshot == with elapsed count=" + elapsedCount);
        if (elapsedCount > this.config.anrSettings.eventsBeforeANRDeclared) {
            UIALogger.logDebug("duplicate snapshot detected" + this.anrSnapshot);
            throw "anr exception-identical after " + elapsedCount + " events";
        };
    };
};


UIAutoMonkey.prototype.processConditionHandlers = function(conditionHandlers, eventNumberPlus1, target) {
    var mainWindow = target.frontMostApp().mainWindow(); //optimization to let handlers do less work. Assumes isTrue() doesn't alter the mainWindow.
    for (var i = 0; i < conditionHandlers.length; i++) {
        var aCondition = conditionHandlers[i];
        if ((eventNumberPlus1 % aCondition.checkEvery()) != 0) {
            continue; //not yet time to process aCondition.
        }
        try {
            UIATarget.localTarget().pushTimeout(0);
            var isConditionTrue = aCondition.isTrue(target, eventNumberPlus1, mainWindow);
        }
        finally {
            UIATarget.localTarget().popTimeout();
        }
        if (isConditionTrue) {
                aCondition.handle(target, mainWindow);
                if (aCondition.isExclusive()) {
                    break;
                } else {
                    mainWindow = target.frontMostApp().mainWindow(); //could be stale
                }
        };
    };

};

UIAutoMonkey.prototype.triggerRandomEvent = function() {
    var name = this.chooseEventName();
    // Find the event method based on the name of the event
    var event = this.allEvents[name];
    event.apply(this);
};

UIAutoMonkey.prototype.target = function() {
    // Return the local target.
    return UIATarget.localTarget();
};

UIAutoMonkey.prototype.delay = function(seconds) {
    // Delay the target by `seconds` (can be a fraction)
    // Defaults to setting in configuration
    seconds = seconds || this.config.delayBetweenEvents;
    this.target().delay(seconds);
};

UIAutoMonkey.prototype.chooseEventName = function() {
    // Randomly chooses an event name from the `eventsWeight` dictionary
    // based on the given weights.
    var calculatedEventWeights = [];
    var totalWeight = 0;
    var events = this.config.eventWeights;
    for (var event in events) {
        if (events.hasOwnProperty(event)) {
            calculatedEventWeights.push({
                weight: events[event]+totalWeight,
                event: event
            });
            totalWeight += events[event];
        }
    }

    var chosenWeight = Math.random() * 1000 % totalWeight;

    for (var i = 0; i < calculatedEventWeights.length; i++) {
        if (chosenWeight < calculatedEventWeights[i].weight) {
            return calculatedEventWeights[i].event;
        }
    }

    throw "No even was chosen!";
};

UIAutoMonkey.prototype.screenWidth = function() {
    // Need to adjust by one to stay within rectangle
    return this.target().rect().size.width - 1;
};

UIAutoMonkey.prototype.screenHeight = function() {
    // Need to adjust by one to stay within rectangle
    return this.target().rect().size.height - 1;
};

UIAutoMonkey.prototype.randomX = function() {
    var min, max;   

    if (this.config.frame){
        // Limits coordinates to given frame if set in config
        min = this.config.frame.origin.x;
        max = this.config.frame.size.width + min;
    } else {
        // Returns a random X coordinate within the screen rectangle
        min = 0;
        max = this.screenWidth();
    }

    return Math.floor(Math.random() * (max - min) + min) + 1;
};

UIAutoMonkey.prototype.randomY = function() {
    var min, max;

    if (this.config.frame){
        // Limits coordinates to given frame if set in config
        min = this.config.frame.origin.y;
        max = this.config.frame.size.height + min;
    } else {
        // Returns a random Y coordinate within the screen rectangle
        min = 0;
        max = this.screenHeight();
    }

    return Math.floor(Math.random() * (max - min) + min) + 1;
};

UIAutoMonkey.prototype.randomTapCount = function() {
    // Calculates a tap count for tap events based on touch probabilities
    if (this.config.touchProbability.multipleTaps > Math.random()) {
        return Math.floor(Math.random() * 10) % 3 + 1;
    }
    else return 1;
};

UIAutoMonkey.prototype.randomTouchCount = function() {
    // Calculates a touch count for tap events based on touch probabilities
    if (this.config.touchProbability.multipleTouches > Math.random()) {
        return Math.floor(Math.random() * 10) % 3 + 1;
    }
    else return 1;
};

UIAutoMonkey.prototype.randomTapDuration = function() {
    // Calculates whether or not a tap should be a long press based on
    // touch probabilities
    if (this.config.touchProbability.longPress > Math.random()) {
        return 0.5;
    }
    else return 0;
};

UIAutoMonkey.prototype.randomRadians = function() {
    // Returns a random radian value
    return Math.random() * 10 % (3.14159 * 2);
};

UIAutoMonkey.prototype.takeScreenShotIfItIsTime = function() {
    var now = (new Date()).valueOf();
    if (!this._lastScreenshotTime) this._lastScreenshotTime = 0;

    if (now - this._lastScreenshotTime > this.config.screenshotInterval * 1000) {
        var filename = "monkey-" + (new Date()).toISOString().replace(/[:\.]+/g, "-");
        this.target().captureScreenWithName(filename);
        this._lastScreenshotTime = now;
    }
};

// If you want to control when the monkey is released please follow the pattern in the SampleCustomization folder. In brief you want to set a global
// as set in SetGlobals.js, but due to Apple's javascript implementation you cannot simply set it before you import UIAutoMonkey.js.
//
if (typeof UIAutoMonkeyClientWillReleaseTheMonkey == 'undefined' || !UIAutoMonkeyClientWillReleaseTheMonkey) {
    // the variable is not defined or it's defined and false
    UIALogger.logDebug("Releasing the monkey directly from UIAutoMonkey"); //explain why it was released to aid in problem resolution.
    (new UIAutoMonkey()).RELEASE_THE_MONKEY();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末见剩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子固翰,更是在濱河造成了極大的恐慌羹呵,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歉铝,死亡現(xiàn)場離奇詭異太示,居然都是意外死亡,警方通過查閱死者的電腦和手機臼勉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門呀非,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事降允∫彰樱” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵翅楼,是天一觀的道長毅臊。 經(jīng)常有香客問我黑界,道長,這世上最難降的妖魔是什么蚯撩? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任烛占,我火速辦了婚禮忆家,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弦赖。我一直安慰自己,他們只是感情好沼沈,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布列另。 她就那樣靜靜地躺著,像睡著了一般页衙。 火紅的嫁衣襯著肌膚如雪店乐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天腺兴,我揣著相機與錄音廉侧,去河邊找鬼段誊。 笑死,一個胖子當(dāng)著我的面吹牛连舍,可吹牛的內(nèi)容都是我干的烟瞧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼强岸,長吁一口氣:“原來是場噩夢啊……” “哼砾赔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妓盲,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤悯衬,失蹤者是張志新(化名)和其女友劉穎檀夹,沒想到半個月后策橘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜亿,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡买决,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年督赤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旁仿。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡孽糖,死狀恐怖毅贮,靈堂內(nèi)的尸體忽然破棺而出滩褥,到底是詐尸還是另有隱情,我是刑警寧澤瑰煎,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布酒甸,位于F島的核電站,受9級特大地震影響沽瘦,放射性物質(zhì)發(fā)生泄漏农尖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一助隧、第九天 我趴在偏房一處隱蔽的房頂上張望并村。 院中可真熱鬧,春花似錦橘霎、人聲如沸姐叁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽处窥。三九已至,卻和暖如春谒麦,著一層夾襖步出監(jiān)牢的瞬間哆致,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工耻蛇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胞此,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓夺蛇,卻偏偏與公主長得像蚊惯,于是被迫代替她去往敵國和親灵临。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,770評論 25 707
  • 從前有種測試叫瞎點測試,隨機測試(淘氣的猴子)波闹。Android 上有 Monkey酝豪。我們以前用 Monkey 來跑...
    iOS開發(fā)周立賀閱讀 7,091評論 29 37
  • 【今日話題】在人數(shù)眾多的社群里,你更傾向于幫助什么樣的人精堕?(大家其實可以這么剖析這個問題:除了這個人的某些能力孵淘、特...
    vickyzhou_jsjs閱讀 149評論 0 0
  • 上月入手窮查理寶典。翻閱幾頁后歹篓,驚為天人瘫证。背后冒汗,那些一個個真實的規(guī)律呈現(xiàn)在眼前庄撮。呈實在眼前是那些多方驗證背捌,時間...
    羽辰在精進閱讀 655評論 0 0
  • 還是那么容易掉眼淚 看著小說電影哭的無法自已 盡管不再是學(xué)生了 還總愛穿襯衫搭毛衣 下班后就不接非親人朋友的電話 ...
    讀云軒札記閱讀 212評論 0 0