Walking Skeleton
A “walking skeleton” is an implementation of the thinnest possible slice of real functionality that we can automatically build, deploy, and test end-to-end.
from Steve, Freeman; Pryce Nat. Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck)) (p. 32). Pearson Education. Kindle Edition.
會走路的骨架是 GOOS 中介紹的一個概念,在提它之前我們先回想一下,在 GOOS 開頭的驗收測試 (Acceptance Tests):
- 先寫一個失敗的 acceptance test (開始第 1 個紅燈階段)
- 接著,進入寫一個失敗的 unit test
- 讓測試通過 (綠燈)
- 看情況要不要重構它
- 回到外圈的通過 acceptance test
而 Walking Skeleton 是在這個循環之外的事,他算是個開發前置的準備工作。也就是 Iteration Zero 的部分:
在這個正式啟動開發之前,需要蠻多前置作業的。回想起剛作為菜鳥開發者時,都不太需要經歷這個階段,因為比較資深的同事會將 Problem Space 與大致的設計弄好,但也許不包含 Automate 的部分 (因為那年代,還沒有讓 CI 概念的能見度變高,而 CD 也還未被推廣):
Iteration Zero 大致上會是打通上圖的流程,對應回遊戲微服務計劃的練習,我們透過什麼方式 Understand the problem 與 Board-Brush Design 呢?
我們使用了 Event Storming 去 modeling 複雜的流程,將所有可能的路徑視覺化地攤開在眼前,而針對單一的、概念較複雜的 User Story 採用 Example Mapping 的方式舉例說明,到最後我們有個基本的 OOA 決定好一致的類別與物件。在完成了團隊認知同步後,Iteration Zero 就解決了大半的問題了,接著開發者們就可以建立 Walking Skeleton。
Walking Skeleton 得完成二個目標:
- 開始自動化測試與部署 (簡單地說,你的 CI/CD 要完成,但初期只有 CI 沒什麼問題) → 設好 GitHub Action 跑測試。
- 完成第 1 個以使用者視角進行的測試,並且確認一下 OOA 的成果既可用又合理 (各種類別間的互動方式,並補一些該有的基礎建設,儘可能以 end-to-end 的角度去測試) → 至少把 OOA 內的所有類別都用上,確認一下是可行的。
在完善了整套流成後,可以隨著 Iteration 的進展,配合 CI/CD 得到持續性的 Feedback loop (凡事求個圓):
- Production Release 了,經使用者用了之後,也許要調整我們對 Problem Space 的認知。
- Deployment 的過程,也可能發現了流程可以再優化,讓 Deployment 更加順利。
- 當然 Deployment,也有機會回到 Design 的部,當產品在長大的過程,Requirements 有機會長出互斥的內容,這時就要重新梳理,再給個新的版本或拒絕一些需求。
到底怎麼開始呢?
先盤點一下我們現在有的知識成果:
- Event Storming
- Example Mapping
- Object-oriented Analysis (OOA )
若是稍為對照一下這個經典的 The Clean Architecture 大餅,目前手上擁有的知識是蛋黃區的 Entities
:
要讓專案可以開始動起來,似乎差點什麼。以上圖來說,玩家與遊戲伺服器互動的流程得經歷:
而遊戲伺服器回應的流程則是反向的,那麼以極簡的風格來達到「使用者視角的測試路線」會是留下:
在 Use Case 中包含了核心的業務邏輯,而 Entities 其中具代表性的 Aggregate Root 也包含了固定規則 (invariants),有了這二者則可以完整表達整個系統的核心,並且是由使用者參與觀點的完整體系。我就可以此為核心去製作 Walking Skeleton 的雛形,但不要太貪心,試著設計一個小一點的測試情境。
以下列紅筆的路線為例 (實作內容介紹可參考《遊戲:玩命土司》):
在設置遊戲時,我們將情境弄成有 2 名玩家,並且他們的「玩家牌堆」已經有若干張牌了,但是 只剩一名玩存活
或是 供應牌堆沒有牌可抽
,滿足結束遊戲的條件。透過這個案例,我們可以製作相關的 Use Cases
- 建立遊戲 (包含了設置遊戲)
- 開始玩家回合
且驗證了「開始玩家回合」的固定規則:
- 遊戲進入終點的條件
- 遊戲算分的機制
用三層式架構或直接刻 API 該怎麼做呢?
先前主要在思考,不想引入「外部」互動的前提下製作 Walking Skeleton 的第 1 組 Acceptance Test 的策略。你完全可以不用理會 Clean Architecture,只要有適當的隔離 Domain Model 即可。
先換個角度想,在 Controller 呼叫 UseCase 時,會將 Input 參數設計成簡單的 Data Object,而 Output 參數也是這樣的型式,為了是隔離 Domain Model 與外部的環境。先站穩自己是個「遊戲伺服器」,再以使用者的視角來看,你 Acceptance Test 的輸入也會是簡單的資料型態。
具體的觀測方向:
你不會在測試中直接摸到 Domain Model
而是透過在三層式中的 Service 或 Manager (老梗笑話:當一個東西,不知道該做什,也不知道該為誰負責,則稱之為 Manager。) 去跟後面的 Database 互動取來 Domain Model,經過了 Service 處理「商業邏輯」後,再度轉成簡單的資料型態。
最終要驗證的就是在多個 Service 交互動用後,遊戲伺服器會達到期望的狀態 (就是驗那個 Input / Output 的 Data Object) 仍是合理的。同樣的,在 API 的情境,就是直接看 Input Parameters 與 Output 的 JSON Content 囉。
實作第 1 個 Acceptance Test
實作 Acceptance Test 其實就是用先前討論的情境完成 end to end 的流程:
而過程中以 TDD 風格來進行:寫程式如許願,先寫出願望再說。至於怎麼實現,事後再補上囉。
We like to start by writing a test as if its implementation already exists, and then filling in whatever is needed to make it work — what Abelson and Sussman call “programming by wishful thinking” [Abelson96]. Working backwards from the test helps us focus on what we want the system to do, instead of getting caught up in the complexity of how we will make it work.
中間冗長又樸實無華的過程,請參考影片:
設定 CI 讓測試跑起來
這份練習會放在 GitHub 上,他有著方便的 GitHub Action 可以自動化各種大小事情,包含最主要的工作:持續性整合。
設定完成後,我們可以在每一次的 PR 中看到測試結果與相關的資訊:
- https://github.com/qtysdk/AllesKase/pull/1
- https://github.com/qtysdk/AllesKase/blob/main/.github/workflows/run-test.yml
實作後的自省
會特別想理解 Walking Skeleton 是因為這頁投影片,其中提到了 那天
還不會提到,但又接在「驗收測試驅動開發」與「測試驅動開發」的前置作製之一:
試著自己問問自己,有沒有 Walking Skeleton 有沒有關係呢?其實也可以依著過去習慣的 TDD 開始實作起來,但最近在不同的 Workshop 中聽到,讓我開始對他好奇了,到底什麼是 Iteration Zero,裡面該做些什麼呢?
相對於實戰技法的 ATDD、TDD 或 BDD,在比這更前期的準備工作,以及為什麼這麼做有好處。就像是技術與品味的問題,二方的涵養要相符才可以有好的輸出表現。以 Walking Skeleton 作為開局的方式來滿足:
- 擁有第一組 end to end 測試
- 可以自動化測試的流程
後續則是進入了日常的開發流程,補上更多的使用情境的測試,在粗略實作過的測試中,補上更多的細節。還有完整地,滿足 OOA 中抓出來的固定規則。