NOTE · 2026-05-09
Unity Personal license の CI activation が完全廃止された (2026-05): dotnet 姉弟 .csproj に逃がす
Unity Personal を使った GitHub Actions の自動テストが、game-ci の deprecation と Unity 公式 manual activation の Personal 拒否で二重に死んでいた。Pro 移行 ($2,200/年~) も self-hosted runner も避けたかったので、Domain 層の noEngineReferences を活かして姉弟 .csproj を立て、dotnet test に逃がした。ubuntu-latest で 43 秒 6/6 pass。
新しく立てた個人 Unity プロジェクト (AtmoTest) で GitHub Actions に Unity Test Framework を載せようとしたら、Unity Personal license の CI activation が 2026-05 時点で完全に閉じている ことに正面衝突した。game-ci の従来手順 (.alf を CI で生成 → license.unity3d.com で .ulf 取得 → UNITY_LICENSE secret 登録) は二重に死んでいて、Pro 移行 (年 $2,200~) も self-hosted runner も個人プロジェクトには重い。結局「Domain 層を Unity ランタイム抜きで dotnet test に流す」姉弟 .csproj 戦略に切り替えた。ubuntu-latest で 43 秒 6/6 pass、無料、Editor 不要。
何が「二重に死んでいる」か
3 層に分けて記録しておく。同じ罠を踏む人が続くはず。
1. game-ci の activation action が deprecated
game-ci/unity-request-activation-file@v2 を CI で実行すると:
This action is no longer supported. Please use the updated activation
instructions found at https://game.ci/docs/github/activation
と出てコケる。replacement の workflow action は提供されておらず、docs ページは「ローカル CLI で .alf を作って license.unity3d.com に出して .ulf を貰え」と人手手順を案内している。
2. ローカル CLI で .alf を作る回避策も死んでいる
Unity Editor を閉じた状態で
/Applications/Unity/Hub/Editor/<ver>/Unity.app/Contents/MacOS/Unity \
-batchmode -nographics -quit -createManualActivationFile -logFile <path>
を叩くと .alf 自体は生成される。今回 Unity_v6000.0.74f1.alf (786 bytes) ができた。しかし license.unity3d.com/manual のアップロード画面に
Unity no longer supports manual activation of Personal licenses.
と書いてあって、Personal の .alf は受け付けない。
3. Unity 公式が Personal の manual activation 廃止を確認
https://docs.unity3d.com/Manual/ManualActivationGuide.html に "The manual activation method doesn't work with floating license subscriptions or Unity Personal." と明記されている。Personal の活性化は Unity Hub login 経由のみで、CI で再現する公式ルートは存在しない。
つまり Personal license で game-ci unity-test-runner を CI で動かす道は完全に閉じている。
取り得る選択肢の比較
| 選択肢 | コスト | Pro | Con |
|---|---|---|---|
| dotnet 姉弟 .csproj | 設定のみ | CI 高速、Unity 不要、無料 | Domain 層に閉じる (Presentation/Bootstrap は別) |
| self-hosted runner | macOS 常時起動 | UTF そのまま使える | デーモン管理、セキュリティ、消費電力 |
| Unity Pro 移行 | 年 $2,200~ | 公式ルート、UNITY_SERIAL で一発 | 個人プロジェクトには重い |
| Unity Cloud Build | 別サービス契約 | Unity 公式 | 別契約、CI 統合別 |
| CI で諦める | ゼロ | 即不通解消 | regression がすり抜ける |
「Domain 層は POCO で純 C# として書く」layered architecture が既に入っているなら、姉弟 .csproj が断然安い。AtmoTest はそれが入っていたのでこれを採用した。
採用案: dotnet 姉弟 .csproj
AtmoTest は AtmoTest.Domain.asmdef に "noEngineReferences": true を設定して、Domain 層を UnityEngine 参照禁止の POCO に強制している。この制約のおかげで Domain 層のテストコードは純粋な C# + NUnit のみで、Unity ランタイム抜きでもコンパイル・実行できる。
つまり Unity 側の asmdef ベース構造はそのまま残し、CI 用に並走する .csproj を 1 本足すだけで、ソース二重管理なしに dotnet test が動く。
構成
<repo-root>/
├── Assets/
│ ├── Scripts/Domain/ ← noEngineReferences: true な POCO
│ └── Tests/EditMode/ ← UTF asmdef、Domain のみ参照
└── tests-net/
└── AtmoTest.Tests.csproj ← Unity 側の .cs を <Compile Include><Link> で参照
.csproj の要点
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="../Assets/Scripts/Domain/**/*.cs">
<Link>Domain/%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
<Compile Include="../Assets/Tests/EditMode/**/*.cs">
<Link>Tests/%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
</Project>
3 つだけ覚えておく必要がある。
EnableDefaultCompileItems=falseは必須: これを忘れると tests-net/ 配下の (空の) .cs を漁ろうとした上で、<Compile Include>で取り込んだ Domain ソースと重複してerror CS0579(重複属性) やCS0260(partial class 不一致) を吐く。- NUnit は 3.x 系を選ぶ: NUnit 4 は
Assert.AreEqualを Classic からNUnit.Framework.Legacy.ClassicAssertに分離しているので、Unity 同梱 NUnit (3.5.x) で書いた既存テストコードがコンパイルできない。3.14.0 が NUnit 3 系の最新かつ Unity と同じ major で齟齬なし。 <Link>メタデータは IDE 表示専用: Domain/ と Tests/ の仮想フォルダを Visual Studio / Rider で見やすくする飾り。実体パスは Unity 側のままで、コンパイル結果には影響しない。
.gitignore の Unity テンプレに *.csproj ブランケット除外があるので !tests-net/*.csproj 例外を入れる。tests-net/{bin,obj}/ の build output も追加 ignore。
CI workflow
name: Tests
on:
push: { branches: [main] }
pull_request: { branches: [main] }
workflow_dispatch:
jobs:
domain:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: 8.0.x }
- run: dotnet restore
working-directory: tests-net
- run: dotnet test --no-restore --logger "console;verbosity=normal"
working-directory: tests-net
実測: ubuntu-latest で 43 秒で 6/6 pass (setup-dotnet 22s + restore 4s + test 9s + その他)。game-ci の Docker pull (3〜5 分) と比べて桁違いに速い。
ローカル TDD ループは無傷
Unity Editor 側は AtmoTest.Tests.EditMode.asmdef を使い続ける。Unity Test Framework + MCP for Unity の mcp__UnityMCP__run_tests で Red → Green を回す日常運用は完全に従来通り。ソースの単一の真実は Unity 側 .cs ファイル、姉弟 .csproj は CI でこれを linking して同じテストを別ランナーで走らせるだけのミラー。同期は不要。
残るトレードオフ
- Presentation 層と Bootstrap 層は CI 対象外: これらは UnityEngine 参照を持つので姉弟 .csproj に含められない。ロジックを Domain 層に寄せ、Presentation は薄い MonoBehaviour adapter に保つ規律 (= もともとの layered architecture の意図) が CI 安全網の前提になる。
- PlayMode テストは CI で動かない: 必要になったら self-hosted runner を立てるか、PlayMode に依存しないテスト設計に保つ。
- Domain 層の境界が曖昧になるとすぐ破綻する:
noEngineReferences: trueを asmdef で常に強制し続けることが必須。ここが緩むと姉弟 .csproj のコンパイルが落ちる、つまり CI が壊れる形で即時にフィードバックが返る (これは安全機構として機能する)。
おまけ: squash merge の add-then-delete 罠
PR #1 (TDD foundation) に最初から含めていた broken な activate.yml を、戦略変更後に同じ PR 内で git rm した。しかし PR を squash merge すると ブランチ起点 → HEAD の累積 diff が 1 commit に潰れる ので、「PR #1 内では add → delete を打ち消し合って no-op」になる。一方で別 PR (#2) で main に直接 add していた activate.yml は残っており、結果として merge 後の main にファイルが居座った。
教訓: 別 PR で先に main に入った file を削除したい場合、その PR のマージ前に消すのではなく、main から派生した独立 PR (今回 PR #3) で明示削除する。squash merge を使う限り、累積 diff が打ち消し合うパターンは常に注意する。
なぜここで効くか
個人 Unity プロジェクトで「テストはちゃんと CI で回したい、でも Pro は出せない、self-hosted runner も立てたくない」という制約を満たす唯一の現実解は、Domain を POCO に切り出して dotnet test に逃がすこと。Unity ランタイムを CI に持ち込まない設計に倒すと、活性化 / ライセンス / Docker pull の重い依存が全部消えて、43 秒で green が返る世界になる。layered architecture を最初から守っていたことが効いた一例。
(Origin: 2026-05-09。AtmoTest PR #1 / #2 / #3 で実装、final main commit de74dfe。game-ci/unity-request-activation-file@v2 deprecated → ローカル CLI で .alf 生成成功 → license.unity3d.com が Personal 拒否 → Unity 公式 manual activation 廃止確認 → 戦略転換、の順で行き詰まりを 1 日で抜けた。)