TestEZ完全導入ガイド — Roblox/Luauプロジェクトにユニットテストを追加する方法【2026年版】
TestEZはRoblox社製のLuau向けユニットテストフレームワーク。Wally・Rojo・Seleneと組み合わせた導入手順をステップバイステップで解説します。
TestEZとは? — Roblox公式のLuauユニットテストフレームワーク
TestEZはRoblox社が開発しオープンソース化したLuau専用のユニットテストフレームワークです。Jasmine/MochaライクなBDDスタイル(describe/it/expect)を採用しており、Roblox Studio内部でも利用されている実績ある信頼性の高いツールです。
TestEZを導入するメリット
TestEZをプロジェクトに導入することで次のメリットが得られます。(1) リグレッション防止: コード変更後に既存機能が壊れていないことを即座に確認できます。(2) リファクタリング時の安全性: テストがあることで大胆なコード整理が可能になります。(3) ドキュメント化: テストコード自体が仕様書として機能します。(4) CI連携: wally installまでの工程をGitHub Actionsで自動化でき、チーム全体の品質を均一に保てます。
前提条件
本ガイドを進めるには以下が必要です。Roblox Studio(最新版)、Rojo(VSCodeとStudioの双方向ファイル同期)、Rokit(ツールバージョン管理)、Wally(Luauパッケージマネージャー)、Selene(静的解析リンター)。これらが未導入の場合は各公式リポジトリのREADMEを参照してください。
ステップ1: Wallyのセットアップ
RokitでWallyを追加します。
# rokitでwallyを追加
rokit trust UpliftGames/wally
rokit add UpliftGames/wally
wally --versionバージョンが表示されれば導入完了です。
ステップ2: wally.tomlにTestEZを追加
プロジェクトルートの `wally.toml` に `[dev-dependencies]` セクションを追加します。
[package]
name = "your-name/your-project"
version = "0.1.0"
registry = "https://github.com/UpliftGames/wally-index"
realm = "shared"
[dev-dependencies]
TestEZ = "roblox/testez@0.4.1"その後 `wally install` を実行すると `DevPackages/TestEZ.lua` が自動生成されます。
ステップ3: RojoプロジェクトにDevPackagesをマッピング
`default.project.json` の `ReplicatedStorage` ノードに `DevPackages` フォルダをマッピングします。
"ReplicatedStorage": {
"$path": "src/ReplicatedStorage",
"DevPackages": {
"$path": "DevPackages"
}
}これによりStudio内の `ReplicatedStorage.DevPackages.TestEZ` からrequireできるようになります。
ステップ4: SeleneにTestEZグローバルを登録
プロジェクトルートに `testez.yml` を作成し、describe/it/expect/beforeAll/beforeEach/afterAll/afterEach をSeleneに認識させます。
globals:
describe:
args:
- type: string
- type: function
it:
args:
- type: string
- type: function
expect:
args:
- type: any
beforeAll:
args:
- type: function
beforeEach:
args:
- type: function
afterEach:
args:
- type: function
afterAll:
args:
- type: function`selene.toml` で `std = "roblox+testez"` を指定してください。
ステップ5: .gitignore設定
Wallyが生成するパッケージはGit管理外にします。
Packages/
DevPackages/
wally.lock`wally.lock` はロックファイルですが、開発環境によって差異が生じるため管理外が無難です。
推奨ディレクトリ構成
以下の構成を推奨します。
| パス | 役割 |
|---|---|
| `wally.toml` | パッケージ定義 |
| `testez.yml` | Selene用TestEZグローバル定義 |
| `DevPackages/` | wally install で生成(Git管理外) |
| `src/ServerScriptService/Tests/` | テストファイル配置ディレクトリ |
| `Tests/RunTests.server.luau` | テストランナー |
| `Tests/*.spec.luau` | 個別テストファイル |
テストランナーの作成
F5(Play)で自動実行されるサーバースクリプトを作成します。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestEZ = require(ReplicatedStorage:WaitForChild("DevPackages"):WaitForChild("TestEZ"))
-- Tests/フォルダ内の*.specモジュールを再帰的に検出
local specs = {}
local testsFolder = script.Parent
for _, descendant in ipairs(testsFolder:GetDescendants()) do
if descendant:IsA("ModuleScript") and descendant.Name:match("\.spec") then
table.insert(specs, descendant)
end
end
local results = TestEZ.TestBootstrap:run(specs, TestEZ.Reporters.TextReporter)テストの基本構造
テストファイルは `<対象>.spec.luau` という命名規則を使います。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MyModule = require(ReplicatedStorage:WaitForChild("Modules"):WaitForChild("MyModule"))
return function()
describe("myFunction", function()
it("1と2を渡すと3を返す", function()
local result = MyModule.myFunction(1, 2)
expect(result).to.equal(3)
end)
end)
end重要な落とし穴: expectは`return function()`の内側でのみ有効
TestEZはdescribe/it/expectなどのグローバルを`return function()`実行時に動的に注入します。そのため、外側でヘルパー関数を定義し内部でexpectを使うと `attempt to call a nil value` エラーが発生します。 NGパターン:
-- NG: return function()の外でexpectを使用
local function helper(v)
expect(v).to.equal(1) -- ここではexpectがnil!
end
return function()
it("test", function() helper(1) end)
endOKパターン:
-- OK: return function()の内側でヘルパーを定義
return function()
local function helper(v)
expect(v).to.equal(1) -- ここではexpectが注入済み
end
it("test", function() helper(1) end)
endよく使うAssertion API
| API | 用途 |
|---|---|
| `expect(v).to.equal(x)` | 等値比較 |
| `expect(v).to.be.ok()` | nilでない確認 |
| `expect(v).never.to.equal(x)` | 否定 |
| `expect(typeof(v)).to.equal("string")` | 型チェック |
| `expect(fn).to.throw()` | 例外発生確認 |
| `expect(t).to.be.a("table")` | 型アサーション |
ライフサイクルフック
| フック | タイミング |
|---|---|
| `beforeAll(fn)` | describeブロック内で1回(最初) |
| `beforeEach(fn)` | 各itの前 |
| `afterEach(fn)` | 各itの後 |
| `afterAll(fn)` | describeブロック内で1回(最後) |
テスト実行方法
(1) ターミナルで `rojo serve` を実行してRojoサーバーを起動。(2) Roblox StudioのRojoプラグインから「Connect」をクリック。(3) F5でPlayを開始。(4) Outputウィンドウを開いてテスト結果を確認します。Outputウィンドウはデフォルトではキーボードショートカットが割り当てられていないため、メニューバーの「View」タブから「Output」をクリックして開きます(必要に応じて File → Advanced → Customize Shortcuts でカスタムショートカットを設定可能)。 サンプル出力例:
[TestEZ] 3 passed, 0 failed, 0 skippedCI統合 — GitHub Actionsでの自動化
`wally install` までの工程はGitHub Actionsで自動化できます。テスト実行自体はRoblox Studio依存のため現状は手動確認が必要ですが、将来的に `run-in-roblox` によるHeadless Studio実行で自動化が期待されます。
- name: Verify Wally
run: wally --version
- name: Install Wally packages
run: wally installFAQ — TestEZに関するよくある質問
Q: TestEZは無料で使えますか? A: はい。MIT Licenseで公開されており、商用プロジェクトを含め完全無料で利用できます。 Q: Studio以外で実行できますか? A: 公式の前提はRoblox Studio環境です。ただし `run-in-roblox` ツールを使うことでHeadless実行も可能です。 Q: 型アノテーションは必須ですか? A: 必須ではありませんが、Luau Strict Modeと組み合わせることでバグをより早期に検出できるため推奨します。 Q: JestやMochaと書き方は同じですか? A: 基本的に同じBDDスタイル(describe/it/expect)なので、JavaScriptテストの経験者はすぐに馴染めます。 Q: Async処理のテストはできますか? A: Roblox用のPromiseライブラリ(evaera/promise等)と組み合わせることで非同期テストが可能です。 Q: カバレッジ測定は対応していますか? A: TestEZ本体には公式のカバレッジサポートはありません。別途カバレッジツールが必要です。 Q: 複数ファイルで共通のヘルパーを使いたい場合は? A: ヘルパーをModuleScriptとして分離し、各specファイルでrequireしてください。`return function()`の内側でのみexpect等が使える点に注意してください。
Roblox/Luauプロジェクトの品質向上をOflightがサポートします
TestEZを含むRoblox開発環境の構築、Luauコードのリファクタリング支援、テスト設計など、ソフトウェア開発全般にわたる技術支援をOflightが提供しています。開発チームの生産性向上や品質管理体制の整備にご興味のある方はお気軽にご相談ください。 ソフトウェア開発支援サービスを見る
お気軽にご相談ください
お問い合わせ