株式会社オブライト
Software Dev2026-04-17

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で自動化でき、チーム全体の品質を均一に保てます。

Loading diagram...

前提条件

本ガイドを進めるには以下が必要です。Roblox Studio(最新版)、Rojo(VSCodeとStudioの双方向ファイル同期)、Rokit(ツールバージョン管理)、Wally(Luauパッケージマネージャー)、Selene(静的解析リンター)。これらが未導入の場合は各公式リポジトリのREADMEを参照してください。

ステップ1: Wallyのセットアップ

RokitでWallyを追加します。

bash
# rokitでwallyを追加
rokit trust UpliftGames/wally
rokit add UpliftGames/wally
wally --version

バージョンが表示されれば導入完了です。

ステップ2: wally.tomlにTestEZを追加

プロジェクトルートの `wally.toml` に `[dev-dependencies]` セクションを追加します。

toml
[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` フォルダをマッピングします。

json
"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に認識させます。

yaml
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管理外にします。

gitignore
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)で自動実行されるサーバースクリプトを作成します。

lua
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` という命名規則を使います。

lua
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パターン:

lua
-- NG: return function()の外でexpectを使用
local function helper(v)
    expect(v).to.equal(1) -- ここではexpectがnil!
end
return function()
    it("test", function() helper(1) end)
end

OKパターン:

lua
-- 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 skipped
Loading diagram...

CI統合 — GitHub Actionsでの自動化

`wally install` までの工程はGitHub Actionsで自動化できます。テスト実行自体はRoblox Studio依存のため現状は手動確認が必要ですが、将来的に `run-in-roblox` によるHeadless Studio実行で自動化が期待されます。

yaml
- name: Verify Wally
  run: wally --version

- name: Install Wally packages
  run: wally install

FAQ — 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が提供しています。開発チームの生産性向上や品質管理体制の整備にご興味のある方はお気軽にご相談ください。 ソフトウェア開発支援サービスを見る

お気軽にご相談ください

お問い合わせ