聊聊集成
從業(yè)務(wù)域角度看集成,首先要定義什么是單元,傳遞一個(gè)命令就是單元,。
那么,在這個(gè)領(lǐng)域里,我們的測(cè)試應(yīng)該有多少個(gè)呢?
首先,要看我們有多少個(gè)變化因子,marsrover面向的四個(gè)不同方向狀態(tài)在接受三個(gè)命令時(shí)的處理是不一樣的。所以四乘三共有12個(gè)case。這些都是單元測(cè)試。測(cè)試的是單個(gè)命令的場(chǎng)景。
接著需要考慮場(chǎng)景之間的聯(lián)動(dòng),也是一種集成測(cè)試。那你可能需要發(fā)帶有幾個(gè)命令的字符串,看看結(jié)果是不是對(duì)的。
happy path完了,再考慮一下異常分支,沒發(fā)送命令是一個(gè)case,發(fā)送了不支持的命令也是一個(gè)case。后者好像沒說要怎么處理呢,記得澄清。本文就不深入聊這些細(xì)節(jié)了。
另外,由于本題沒有提出最大長(zhǎng)度問題,這個(gè)我們可以先不測(cè),但是這個(gè)是個(gè)技術(shù)原因必須限制的數(shù)字,畢竟單批量發(fā)送還是不限制不行的。根據(jù)我們上一篇,在實(shí)際工作中一定要探討這個(gè)問題,并明確在規(guī)格上發(fā)布給使用者,不要腦補(bǔ),如過業(yè)務(wù)方確實(shí)無(wú)所謂,就由我們提出一個(gè)數(shù)字,雙方都認(rèn)可了,放在一個(gè)地方。
而在方案域,集成則有不同的含義。不同的設(shè)計(jì)也會(huì)需要不同的集成測(cè)試。
先說分層,我會(huì)把與外面交互的部分劃為一層,也就是把“0 0 N”和“MLRR”之類解析成領(lǐng)域概念的為一層。
解析完之后處理核心業(yè)務(wù)邏輯的部分再分一層,靠main或XXController之類的調(diào)度。
那么這兩層的集成呢就是方案域的集成測(cè)試了。不過這個(gè)時(shí)候,你會(huì)發(fā)現(xiàn),解析的部分單獨(dú)測(cè),解析出來(lái)的結(jié)果執(zhí)行的部分單獨(dú)測(cè),最后他們集成起來(lái)的部分只需要mock測(cè)試測(cè)一個(gè)用例就好了。
不但測(cè)試簡(jiǎn)單了,擴(kuò)展其實(shí)也會(huì)簡(jiǎn)單,這樣我們將來(lái)如果命令發(fā)生變化比如,開始這么寫命令 “M2 L R”也就是移動(dòng)的時(shí)候加入了數(shù)字控制移動(dòng)多少格,為了好解析,我們加入了空格來(lái)分隔。這種變化對(duì)于執(zhí)行的來(lái)說,并無(wú)感知,因?yàn)檫@個(gè)變化悲傷層負(fù)責(zé)解析的部分已經(jīng)隔離掉了。
聊聊設(shè)計(jì)
這個(gè)題一開始,很多人肯定是一大串if來(lái)解決3個(gè)command的if和四個(gè)方向的if。
寫完之后呢,就會(huì)開始重構(gòu),重構(gòu)成什么樣子的就都有了,比如下面這個(gè):

這個(gè)重構(gòu)完,通常if就沒有了。
但是有很明顯的循環(huán)依賴,所有人都能改MarsRover,這封裝性也太差了。那我們提取一個(gè)數(shù)據(jù)傳輸對(duì)象來(lái)提升一下封裝性吧,像下面這樣:

這么做循環(huán)依賴貌似少了,封裝性也提高了,但Direction和RoverStatus還是有個(gè)循環(huán)依賴,進(jìn)一步消除一下試試:

終于,所有的循環(huán)依賴都干掉了,但這圖看著好復(fù)雜啊而且有個(gè)問題,那就是Command和DIrection嚴(yán)重耦合,Direction的三個(gè)方法明顯是Command的延伸,每加一個(gè)Command,Direction脫不了也要加個(gè)方法。這個(gè)設(shè)計(jì)肯定是不好的。我們可以發(fā)現(xiàn)DirectionValue實(shí)際上是可以不知道Move的相關(guān)概念的,方向之間自然的有左右關(guān)系,所以可以改造成這樣的設(shè)計(jì):

每個(gè)DirectionValue都有一個(gè)index,左轉(zhuǎn)可以通過+3然后%4的方式完成,右轉(zhuǎn)可以通過+1然后%4的方式完成,不需要外面的其他概念,實(shí)現(xiàn)也很簡(jiǎn)單。
這下貌似清靜了,但實(shí)際上Move這個(gè)Command里面有四個(gè)if還是不好消掉,如果因此搞四個(gè)新的子類,會(huì)有點(diǎn)啰嗦。這個(gè)時(shí)候如果采用函數(shù)式的方式,把DirectionValue的四個(gè)值和對(duì)應(yīng)的移動(dòng)算法用一個(gè)Map封裝起來(lái),其實(shí)就比較簡(jiǎn)單了。
這個(gè)題做到這,可以加入一些新需求,比如:
新需求1,加一個(gè)新指令,如果接受到B指令,那么就會(huì)進(jìn)入倒車狀態(tài),這個(gè)時(shí)候M跟正向的時(shí)候是反的。注意,指令的操作雖然反了,但是朝向不能變。比如朝北的M之后,y坐標(biāo)是減了,但是朝向必須還是北。
新需求2,有一個(gè)雷達(dá)功能,執(zhí)行完判斷一下自己是不是掉溝里了,如果掉溝里了,就再map上打個(gè)記號(hào)X(只是表達(dá)這個(gè)意思,不一定非要是字符串X),后面的rover會(huì)忽略走向這個(gè)記號(hào)的命令(當(dāng)火星車掉到溝里時(shí),調(diào)用init方法創(chuàng)建一輛新的火星車,但舊的火星車還要在溝里,不能消失)。判斷無(wú)法動(dòng)的方式目前先用隨機(jī)數(shù)吧,正好練練mock。
新需求3,地圖有不同的地形,有的地形能觸發(fā)無(wú)法移動(dòng),有的不能。掉溝里是火星車自己在地圖上打的標(biāo)記,不同地形是火星車在來(lái)到火星時(shí)就知道的地圖信息(考慮,X標(biāo)在哪?真實(shí)的地圖上嗎?真實(shí)的世界的映射的地圖,和標(biāo)記了X的地圖應(yīng)該是個(gè)什么關(guān)系?)
新需求4,車也有狀態(tài),有的狀態(tài),車會(huì)忽略一些指令,比如左轉(zhuǎn)壞了,會(huì)忽略左轉(zhuǎn)指令
新需求5,車還分類型,比如Bus,占兩格,他的坐標(biāo)是車頭的坐標(biāo),但是它左拐時(shí)周邊必須有可以拐的空間(右側(cè)兩格都不能是X),否則會(huì)忽略掉指令。
Bus可能會(huì)在拐彎時(shí)壞掉,
引入卡車,卡車占兩格,不同于Bus的是,卡車如果車頭處沒有壞掉,可以接受特殊指令來(lái)脫鉤車頭,其他車接受這個(gè)指令無(wú)反應(yīng)。