---
title: "在 Game Jam（黑客松）寫測試有用嗎？"
date: 2023-02-08T00:00:00+08:00
publishDate: 2023-02-08T14:12:15+08:00
lastmod: 2023-03-13T11:04:53+08:00
tags: ["黑客松","Global Game Jam","TypeScript","Vite","測試","Domain-Driven Design","Clean Architecture"]
toc: true
permalink: "https://blog.aotoki.me/posts/2023/02/08/does-write-test-in-game-jam-is-useful/"
language: "zh-tw"
---


在遊戲開發中寫測試一直以來都是被認為相當困難的一件事情，雖然有很多人嘗試，但我們仍沒有找到一個很好的方法解決。

同時，寫測試對許多人來說是一種拖慢開發速度的工作，這次我在每年都會參加 Global Game Jam 中挑戰用 48 小時的時間開發，運用這段時間學到的 Domain-Driven Design（領域驅動設計）、Clean Architecture（清楚架構）、敏捷開發、ATDD（驗收測試驅動開發）等技巧來做實驗。

* 遊戲：[https://ggj2023.aotoki.cloud/](https://ggj2023.aotoki.cloud/)（可能要等一下）
* 原始碼：[https://github.com/elct9620/GlobalGameJam2023](https://github.com/elct9620/GlobalGameJam2023)

<!--more-->

## 時間與取捨{#time-balance}

黑客松最為困難的地方在於有限的時間，因此有經驗的開發者大多會選擇盡全力的限定開發的範圍與排序實作的優先順序，這也是「敏捷開發」的核心精神之一，我們需要捨去無法交付價值的功能，專注在最重要的地方。

過去的經驗中，像是大學的畢業專題（3D 動作遊戲）、工作跟客戶的互動，其實都會遇到這樣的狀況，也經常因為無法取捨而讓最後無法交付完善的成果。不過這次抽到的隊友不論是美術、遊戲企劃、音效都對這件事情有共識，因此我們能集中力氣在完成一款「可以玩」的成品。

> 實際上主辦單位每年都會提醒大家，再加上每組都會有幾位有經驗的參加者，通常都會很好引導隊友做出能夠交出作品的決策。

## 設計與規格{#design-and-spec}

影響後續開發最關鍵的其實是（架構）設計跟規格這兩塊，這一次受限於工具的狀態（我們沒有使用遊戲引擎，也來不及準備企劃能做的自動測試）因此沒有在規格上過多著墨，然而因為遊戲大多會專攻一個機制以及進行事前的討論，因此大多數時候還是相對明確的。

至於架構設計在有多位工程師的團隊中，就扮演非常重要的角色。因為團隊的另一位工程師是跟我一起工作多年的同事（[西瓜](https://pastleo.me/)）因此還是相當有默契，同時選用我們都熟悉的 JavaScript 來開發，以 [PIXI.js](https://pixijs.com/) 為基底，用 [Inversify.js](http://inversify.io/) 和 [RxJS](https://rxjs.dev/) 搭配來進行實作，另外也順便嘗試了 [Vite](https://vitejs.dev/)。

最後的版本大致上是這樣，這是我結束後重新整理的版本，不過前期開發也是以這個為基礎去進行。

![架構圖](images/architecture.jpg)

因為 PIXI.js 在邊界（Bounded Context）是非常強的，基本上所有顯示物件都要放進去主要的容器中（`PIXI.Stage`）這其實會讓遊戲邏輯跟顯示邏輯嚴重的混合再一起，這也是大多遊戲引擎開發時會遭遇到的問題，因此最後我們直接把 PIXI.js 的部分當作獨立的邊界，只處理顯示（或者說輸出）的部分，剩下的邏輯都抽離出來用 `Domain Event`（領域事件）當作介面來交換資訊。

因此在圖上看到的 `Core.KeyboardEvent` 和 `Game.KeyboardEvent` 會是兩種不同的物件（TypeScript 中我們只定義了結構）讓不同領域的邏輯取用需要的部分。

## 重構驅動開發{#refactor-driven-development}

因為我們不是使用遊戲引擎，因此要馬上的開始讓團隊成員看到成果是有一定困難的，因此開發流程比較類似於「重構驅動」的方式，先由西瓜在我們設計的框架下，從 PIXI.js 的輸出端放置美術的素材來跟團隊成員確認，接著繼續堆疊一些遊戲邏輯上去確保能照我們預期的運作，再由我來進行重構。

如果有適合的測試框架，像是前端可以使用 Cypress、Playwright 來做測試的話，前面的步驟基本上就是端對端測試（E2E Testing）來實踐驗收測試驅動開發的自動化版本，但是遊戲大多還沒有成熟的工具做這件事情，因此我們會用人工的方式實踐，這也是過程中壓力最大的一環。

在西瓜完成基本的邏輯後，我則是將這些遊戲邏輯重構，抽離到 Core Domain 裡面，再讓 RxJS 能以 Domain Event 的方式跟兩邊互動，這部分也比較完整的實踐了 Domain-Driven Design 的設計，因此都是以 Use Case 為基礎下去加入測試，最後就順利的保護整個遊戲邏輯。

> 可能會有人好奇遊戲的 Use Case 是怎麼設定的？這個其實就是 Domain-Driven Design 的戰略部分，包含像是需求跟規格的轉換、事件風暴（Event Storming）的技巧，以遊戲來說我們會去「打怪」來做判定，那麼打怪就是一個 Use Case 接下來就是要根據遊戲條件實作像是打擊點、環境的資訊等等機制，來讓「打怪」可以被判定

## 還可以更快{#can-be-faster}

結束後這幾天的回顧，我認為我們在開發上其實還能「更快」雖然有一些條件在台灣的 Global Game Jam 場地規則下不容易達成（一定程度隨機組隊）然而在開發上還是有不少可以改進。

首先我們略過了規格的部分，這點其實在遊戲開發中有不少吃虧的地方。像是轉換 Use Case 的部分，就有幾個實作最後被拔掉或者修正，因為我們在規格上沒有明確定義遊戲規則，最後讓程式在實作的時候「白費力氣」這也是 ATDD 的價值所在，我們先根據想看到的實現，再去拆分就比較不會有多餘的處理。

另一個則是我們的測試機制不完善，這也是大多數情況會認為「寫測試花時間」的原因之一，因為這個原因我們不得不放棄從遊戲畫面的部分開始做驗證，也就間接的讓規格變得模糊，因為我們沒有特別去想這個畫面在某個瞬間的狀態是怎樣，一段時間後又會如何，最後變得得靠多次的人工驗證才找出規律。

最後是熟練度，這應該是導入整個機制最困難的地方。假設整個團隊都有這樣的意識，我們很快就能拿到有精確規格的文件或者資料開始協作，同時我也不需要依靠跟西瓜一起工作的熟練度來達成這件事情，而是可以更細的拆分進行協作也不需要太多確認（我們在 24 小時後才有自動部署，如果一開始就有甚至可以請其他組員協助測試）那麼開發速度還能再更快。

當然，最後的問題是每個開發團隊都要面臨的狀況，而且我們在開發遊戲初期有不少需要驗證的地方，像是使用 FMOD 還是 MIDI 來作為基礎，因為是跟音樂相關的遊戲需要讀取到特定的節奏資訊等等，假設有一定程度的熟練度，應該可以用更短的時間內達成目標，這點就期待下次 Global Game Jam 我能找到更好的方法讓團隊成員快速進入狀況了！

> 這次也是我這麼多次參加結束後，還有動力去修改遊戲的的一次，因為架構規劃已經相對完善，可以在相對不費力的狀況下修改跟重構，也算是讓自己過去幾年研究這個主題的效果得到了一定程度的驗證。

