從數(shù)據(jù)流動(dòng)角度解決測(cè)試難題

上篇講述了增強(qiáng)邏輯功能測(cè)試而改進(jìn)MVC為MVP,但是這樣做可能還不夠徹底,現(xiàn)在來(lái)討論另一個(gè)純粹從測(cè)試角度設(shè)計(jì)的框架。

首先我們來(lái)明確一下炼幔,測(cè)試中最核心的東西是什么。當(dāng)然是數(shù)據(jù)史简,我們永遠(yuǎn)是圍繞著數(shù)據(jù)來(lái)的乃秀,那么之前一些架構(gòu)的問(wèn)題是什么。無(wú)論哪個(gè)框架圆兵,數(shù)據(jù)的流通都是雙向的跺讯,當(dāng)數(shù)據(jù)流通成為單向了會(huì)怎么樣呢?

in data ==> Module ==> out data

這樣我們偽造數(shù)據(jù)進(jìn)行測(cè)試就會(huì)非常方便了殉农。按照這個(gè)思想就有了數(shù)據(jù)單向流通的架構(gòu)刀脏。

數(shù)據(jù)單向流通的實(shí)現(xiàn)

這個(gè)概念最早是在web中提出的,應(yīng)用在React里超凳,官方的方案是Redux∮郏現(xiàn)在swift也提出了一種實(shí)現(xiàn)ReSwift

我在之前寫React的時(shí)候使用過(guò)這種方案轮傍,從開(kāi)發(fā)角度來(lái)說(shuō)暂雹,這種方案會(huì)大大增加開(kāi)發(fā)難度,代碼量也會(huì)大量增加创夜,而且開(kāi)發(fā)思路也需要從以前的思考方式轉(zhuǎn)換過(guò)來(lái)杭跪。但是如果我們把這個(gè)思路轉(zhuǎn)換過(guò)來(lái),其實(shí)對(duì)整個(gè)流程是更加簡(jiǎn)化和分離的驰吓。

從測(cè)試角度看涧尿,我覺(jué)得無(wú)疑是我知道的最可測(cè)的一種框架,甚至可以測(cè)試部分視圖的邏輯檬贰。

那么總的來(lái)說(shuō)姑廉,很難說(shuō)這種結(jié)構(gòu)的好壞,就算不考慮增加的開(kāi)發(fā)時(shí)間偎蘸,也是一種難以給以一種評(píng)價(jià)的方案庄蹋。

(Redux/ReSwift)框架介紹

方案的幾個(gè)核心是:

  • 數(shù)據(jù)的單向流通
  • 每個(gè)視圖都可以看做一個(gè)狀態(tài)機(jī)
  • pure function

關(guān)于pure function,我就不做太多介紹了迷雪,簡(jiǎn)單的說(shuō)限书,就是同一輸入必定會(huì)有相同的輸出,是非常容易測(cè)試的一種函數(shù)章咧。

首先倦西,我們來(lái)看一下官方的架構(gòu)圖。

可以看到赁严,數(shù)據(jù)流動(dòng)方向都是朝一個(gè)方向進(jìn)行的扰柠。那么下面從每個(gè)模塊來(lái)介紹下,還是以star button為例子疼约。

State

視圖狀態(tài)機(jī)卤档,也是所有會(huì)更新界面數(shù)據(jù)保存的地方,可以認(rèn)為相當(dāng)于ViewModel程剥。

首先我們star會(huì)有以下幾種視覺(jué)樣式

enum StarButtonState {
    case star
    case staring
    case unstar
    case unstaring
}

所以State可以定義為

struct StarState: StateType {
    var state: StarButtonState
    var starCount
}

Action

首先我們定義幾種狀態(tài)機(jī)轉(zhuǎn)換的Action類型

struct StarAction: Action { }
struct StaringAction: Action { }
struct UnstarAction: Action { }
struct UnstaringAction: Action { }

以及相應(yīng)的功能以及狀態(tài)變更劝枣,這里異步請(qǐng)求采用延遲來(lái)代表。

func star(id: String) -> Store<StarState>.ActionCreator {
    return { state, store in
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            store.dispatch(StarAction())
        }
        return StaringAction()
    }
}
func unstar(id: String) -> Store<StarState>.ActionCreator {
    return { state, store in
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            store.dispatch(UnstarAction())
        }
        return UnstaringAction()
    }
}

View

視圖層其實(shí)很簡(jiǎn)單织鲸,只需要根據(jù)State的不同來(lái)更新就可以了舔腾。注意的是,更新都是無(wú)狀態(tài)的搂擦,和上一個(gè)狀態(tài)無(wú)關(guān)稳诚,所以view層是個(gè)無(wú)狀態(tài)層。

class StarButton: UIButton, StoreSubscriber {

    let store = Store<StarState>(reducer: starReducer, state: nil)

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.store.subscribe(self)
    }

    func newState(state: StarState) {
        // update UI
    }
}

Reducer

狀態(tài)轉(zhuǎn)換器瀑踢,唯一可以更新State的地方扳还。

func starReducer(action: Action, state: StarState?) -> StarState {
    var state = state ?? StarState(state: .star, starCount: 0)

    switch action {
    case _ as StarAction:
        state.state = .star
        state.starCount += 1

    case _ as StaringAction:
        state.state = .staring

    case _ as UnstarAction:
        state.state = .unstar
        state.starCount -= 1

    case _ as UnstaringAction:
        state.state = .unstaring

    default:
        break
    }

    return state
}

數(shù)據(jù)傳遞

那么最重要的就是數(shù)據(jù)如何傳遞的了。首先要明確的是每個(gè)模塊能夠修改的橱夭,或者說(shuō)是傳遞的普办,只能是下個(gè)模塊。

比如徘钥,用戶star button觸發(fā)了一個(gè)事件:

func onButton(sender: StarButton) {
    if (store.state.state == .unstar) {
        store.dispatch(star(id: id))
    }
    else if (store.state.state == .star) {
        store.dispatch(unstar(id: id))
    }
}

此時(shí)會(huì)創(chuàng)建Action衔蹲,也就是將view事件轉(zhuǎn)換為Action。然后會(huì)傳遞到store中呈础,store會(huì)調(diào)用Reducer進(jìn)行處理舆驶。Reducer更新state之后又會(huì)觸發(fā)store的subscribe事件,回到view的func newState(state: StarState)而钞。

View (User Event)
==(create)==> ActionCreator/Action
==(dispatch)==> Store <--(Update State)--> Reducer
                  \==(subscribe)==> View (newState)

大概的一個(gè)流程就是這樣了沙廉。

接下來(lái)說(shuō)說(shuō)這樣做的模塊化的優(yōu)勢(shì)。

模塊化和測(cè)試性

首先臼节,我們需要有函數(shù)式編程的概念撬陵,函數(shù)也是一等公民珊皿,所以ActionCreatorReducer都是獨(dú)立的模塊。

作為使用者巨税,我們?cè)诓恍枰馦VC一樣知道這些api所代表的操作功能蟋定,相對(duì)應(yīng)的,我們需要去了解一個(gè)模塊的動(dòng)作(Action)草添,比如以上例子就是

func star(id: String)
func unstar(id: String)

這樣的劃分比MVC要友好的多驶兜,真正的把邏輯功能從原本的C中分離開(kāi)。需要觸發(fā)這個(gè)行為也非常簡(jiǎn)單store.dispatch(star(id: id))远寸。相比MVP抄淑,行為更加的獨(dú)立,每個(gè)行為之間完全沒(méi)有聯(lián)系驰后,也不會(huì)產(chǎn)生干擾影響肆资。同時(shí)因?yàn)槊總€(gè)行為的獨(dú)立性,可復(fù)用程度也就越高灶芝。

Reducer則代表了view層的更新迅耘,也可以非常明確的知道每個(gè)狀態(tài)的變更發(fā)生了什么。相比其他模式监署,將界面更新完全交給view或者Controller颤专,Reducer是最明確也是最清晰的。同時(shí)Reducer也是獨(dú)立的钠乏,可以替換的栖秕。

對(duì)于UIkit層面我們無(wú)法單元測(cè)試,所以測(cè)試的主要部分是ActionReducer晓避。這兩個(gè)模塊可以說(shuō)都是pure function或者在某些條件下是pure function的簇捍,所以測(cè)試也非常的簡(jiǎn)單。

對(duì)比

和這個(gè)模式比較像的有狀態(tài)機(jī)模式和Reactive俏拱。

狀態(tài)機(jī)模式也是實(shí)現(xiàn)對(duì)應(yīng)功能暑塑,以及對(duì)應(yīng)狀態(tài),然后通過(guò)子類化的方式去實(shí)現(xiàn)Reducer的功能锅必。

Reactive則比較像ActionCreator事格,只是Reactive返回的是信號(hào)量。

使用場(chǎng)景

從上面可以看出這是一套非常優(yōu)秀的模塊劃分方案搞隐,但同時(shí)也會(huì)大大增加代碼量驹愚,而且需要改變以前的思維模式。而對(duì)于目前國(guó)內(nèi)的現(xiàn)狀來(lái)看劣纲,很難有這么多時(shí)間和精力讓整個(gè)項(xiàng)目都使用這種模式逢捺。

但是這種模式的特點(diǎn)也非常的明顯,在處理比較復(fù)雜的交互行為癞季,并且存在較多的視圖狀態(tài)的時(shí)候劫瞳,會(huì)是一種比較好的方案倘潜。比如視頻播放界面。

所以個(gè)人認(rèn)為志于,在一些簡(jiǎn)單的場(chǎng)景下并不需要使用該方案涮因,但是在一些復(fù)雜的交互頁(yè)面,而且又非常想要引入單元測(cè)試的場(chǎng)景恨憎,可以酌情考慮下這種方案。這種方案要求人們的思維方式的改變郊楣,需要有一定的函數(shù)式編程的概念憔恳。

雖然不一定會(huì)直接使用ReSwift,但是這種思想有很多值得借鑒的地方净蚤,利用這種思想做出類似的效果钥组,以便達(dá)到可以容易進(jìn)行白盒測(cè)試的目的。

最后

以上雖然說(shuō)不會(huì)全部使用該方案今瀑,但也可以部分使用程梦。比如獨(dú)立的小模塊,亦或是app層面的一些東西橘荠。下次可以討論下app層面如何來(lái)利用單向數(shù)據(jù)流來(lái)簡(jiǎn)化流程屿附。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哥童,隨后出現(xiàn)的幾起案子挺份,更是在濱河造成了極大的恐慌,老刑警劉巖贮懈,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匀泊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朵你,警方通過(guò)查閱死者的電腦和手機(jī)各聘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抡医,“玉大人躲因,你說(shuō)我怎么就攤上這事〖缮担” “怎么了毛仪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芯勘。 經(jīng)常有香客問(wèn)我箱靴,道長(zhǎng),這世上最難降的妖魔是什么荷愕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任衡怀,我火速辦了婚禮棍矛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抛杨。我一直安慰自己够委,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布怖现。 她就那樣靜靜地躺著茁帽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屈嗤。 梳的紋絲不亂的頭發(fā)上潘拨,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音饶号,去河邊找鬼铁追。 笑死,一個(gè)胖子當(dāng)著我的面吹牛茫船,可吹牛的內(nèi)容都是我干的琅束。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼算谈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涩禀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起然眼,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤埋泵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后罪治,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丽声,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年觉义,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雁社。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晒骇,死狀恐怖霉撵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洪囤,我是刑警寧澤徒坡,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站瘤缩,受9級(jí)特大地震影響喇完,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剥啤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一锦溪、第九天 我趴在偏房一處隱蔽的房頂上張望不脯。 院中可真熱鬧,春花似錦刻诊、人聲如沸防楷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)复局。三九已至,卻和暖如春粟判,著一層夾襖步出監(jiān)牢的瞬間亿昏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工浮入, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留龙优,地道東北人羊异。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓事秀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親野舶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子易迹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

  • Redux對(duì)于React程序是可有可無(wú)的嗎?當(dāng)你認(rèn)識(shí)到Redux在編程時(shí)給你那種可以掌控一切狀態(tài)能力的時(shí)候,你會(huì)覺(jué)...
    smartphp閱讀 940評(píng)論 0 5
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評(píng)論 25 707
  • 隨著 app 的發(fā)展, MVC 漸漸的滿足不了業(yè)務(wù)的需求平道。大家都在探索各種各樣的架構(gòu)模式來(lái)適應(yīng)這種情況睹欲,像是MVV...
    CepheusSun閱讀 5,489評(píng)論 9 28
  • 主要是自己整理的筆記。
    加加_2a9b閱讀 253評(píng)論 1 0
  • 有一個(gè)美麗的姑娘 笑容像雪蓮花一樣 守候 在遠(yuǎn)方 俏皮 簡(jiǎn)單 可愛(ài)的模樣 善良 大方 光輝的模樣 你有那單純的...
    憶清歡閱讀 1,010評(píng)論 15 11