開発
ガイドEpisode 14

第拾肆話

開発

各プラットフォームでの asuka dev の詳細、デバイス、asuka vet、テスト、よくある問題

asuka dev はアプリを実行し、ファイルを保存するたびにリロードします。実行先は、iOS シミュレーター、Android のエミュレーターまたは実機、手元の Mac・PC・Linux マシン、ブラウザーのいずれかです。このページでは、それぞれの実行先でリロードがどう行われるか、リロードで何が保たれるか、デバイス、アセット、ログ、リロードできないもの、asuka vet、よくある問題、そして Asuka 自体のテストとベンチマークについて説明します。

asuka dev

Shell
asuka dev [flags] [dir]                     # このコンピューター:macOS、Windows、Linux
asuka dev --platform ios [flags] [dir]       # iOS シミュレーター
asuka dev --platform android [flags] [dir]   # Android のエミュレーターまたは実機
asuka dev --platform web [flags] [dir]       # ブラウザー

--platform には ios、android、mac、windows、linux、web のいずれかを指定します。asuka build でも同じように使えます。省略すると、今使っているコンピューター向けにアプリを実行(またはビルド)します。一回の実行で扱えるプラットフォームは一つだけです。二つのプラットフォームで動かすなら、ターミナルを二つ開いてください。

dir はアプリのディレクトリで、デフォルトはカレントディレクトリです。ここには app.Run を呼び出す main パッケージを置きます。フラグは次のとおりです。

  • --platform:上で説明したとおりです。
  • --lang:この実行でだけ表示する言語(de、zh-Hans)か、疑似ロケールを指定します。疑似ロケールは en-XA(アクセント記号付きで長めのテキスト)と ar-XB(右から左)です。--dir rtl を付けると、どのアプリでも右から左にレイアウトします。多言語対応を参照してください。
  • --once:アプリをビルドして一度だけ実行し、ファイルの監視はしません。デスクトップでは、アプリが終了すると asuka dev も終了します。スマートフォンでは、Go のコードを組み込んだアプリをビルドし、インストールして起動したら終了します。ログは表示されません。ブラウザーでは、アプリを配信します。
  • -v:Asuka が実行するコマンドを表示します。
  • --no-vet:ビルドと並行して asuka vet を実行しないようにします。

スマートフォンでは、次のフラグも使えます。

  • --device:iOS ではシミュレーターの名前か UDID を指定します(デフォルトは起動中の iPhone で、なければ最近の iPhone Pro を起動して使います)。Android では adb のシリアル番号か AVD の名前を指定します(デフォルトは adb が最初に挙げるデバイスで、なければ最初の AVD を起動して使います)。
  • --no-hot:ホットリロードを使いません。Go のコードを組み込んだアプリをビルドし、保存するたびにビルドと再インストールをやり直して、まっさらな状態から起動し直します。

ブラウザーでは --port、--lan(スマートフォンのブラウザーから使えるよう、ネットワーク上に配信します)、--no-open が使えます。Web を参照してください。

デスクトップでの実行

macOS、Windows、Linux では、アプリの Go コードとプラットフォームのホストが一つの Go プログラムになり、CGO_ENABLED=0 でこのコンピューター向けにビルドされます。asuka dev はそれをビルドして実行します。保存するたびにビルドし直し、実行中のアプリを画面に残したまま新しいプログラムを起動します。新しいプログラムが起動すると、Asuka は実行中のアプリを停止します。停止するアプリは終了時にシグナルの値を保存し、新しいアプリが起動を続けてその値を引き継ぎます(リロードで保たれるものを参照)。リロードにかかる時間は go build とほぼ同じです。ビルドが失敗した場合はエラーを表示し、実行中のアプリはそのまま残ります。新しいアプリが起動直後に終了した場合(init での panic など)も同様です。

macOS では、プログラムを初めて実行するときにシステムがそのプログラムを検査します。このため、リロードのたびに数百ミリ秒ほど余計にかかることがあり、そのときは Asuka がその旨を表示します。「システム設定」>「プライバシーとセキュリティ」>「デベロッパツール」にターミナルアプリを追加すると、そのターミナルから実行するプログラムではこの検査が行われなくなります(tmux などのマルチプレクサーを使っている場合は、マルチプレクサーも追加してください)。

アプリは assets/ をその場所から直接読むので、アセットを変更してもコピーは必要ありません。アプリのデータ(store.Dir() と、そこに置く SQLite データベース)は、インストールしたアプリのデータとは別に、Asuka のキャッシュ内にあるアプリ専用の data ディレクトリに保存されます(最初からやり直すを参照)。そのため、asuka dev とリリース版がデータを共有することはありません。アプリの出力(fmt.Println、log、panic)はターミナルに表示されます。

macOS。 Mac では、asuka dev はリリース版と同じ形の .app を Asuka のキャッシュ内に作り、そこからアプリを実行します。この .app には、Info.plist(asuka.json の名前、bundle ID、URL スキーム、利用目的の説明文、言語)とアイコンが含まれ、アドホック署名されます。そのため、Finder から開いたときと同じく、システムからはそのアプリ自身として扱われます。Dock にはアプリの名前とアイコンが表示され、通知もアプリ自身のものになり、プライバシーに関する許可の確認(カメラ、位置情報、連絡先)もターミナルではなくアプリに対して、アプリの利用目的の説明文とともに表示されます。プロビジョニングプロファイルのエンタイトルメントが必要なパッケージ(プッシュ通知、Sign in with Apple、App Attest)は、そのプロファイルで署名したリリースビルドでしか動きません。配布を参照してください。

Windows。 asuka dev は Windows 上でしか動きません。一方、asuka build --platform windows はどのシステムからでもビルドできます。開発用のビルドはコンソールプログラムなので、出力はターミナルに表示されます。リリース版にはコンソールがありません。Windows ではプロセスに Ctrl-C を送れないため、リロードのためにアプリを停止するときは、実行ごとに保持している接続を通じて Asuka がアプリに停止を伝えます。アプリは終了時に状態を保存します。

Linux。 Linux では、asuka dev は GTK 4 と libadwaita の上でアプリを実行します。これらがインストールされている必要があります(はじめにを参照)。アプリは、セッションのディスプレイ(Wayland または X11)で動きます。リリース版と同じく、asuka.json の名前、ID、バージョン、アクセントカラーが使われます。Asuka は実行ごとに保持している接続を通じて(または SIGINT で)アプリを停止し、アプリは終了時に状態を保存します。Mac や PC で asuka dev --platform linux を実行すると、アプリは Linux 向けにビルドされますが、その場では実行できません。ただし、ASUKA_LINUX_RUN に、ディスプレイのある Linux マシンでプログラムを実行するコマンドを指定した場合は別です。そのマシンからは、このコンピューターのファイルが同じパスで見える必要があります。たとえば VM で、ASUKA_LINUX_RUN="orb -m ubuntu"(OrbStack)や ASUKA_LINUX_RUN="limactl shell default"(Lima)のように指定します。Asuka はそのコマンドを使い、そのマシンのアーキテクチャー向けにビルドしたアプリを実行します。

スマートフォンでの実行:ホットリロードモードとビルドしたアプリ

iOS には Mac が、Android には Mac か Linux が必要です。デフォルトでは、asuka dev はホットリロードモードで動きます。Go のコードを手元のコンピューターで実行し、ビューを開発用アプリに表示します。開発用アプリは Asuka という名前のアプリ(dev.asuka.player。英語のドキュメントでは player)で、アプリを何も含まないプラットフォームのホストそのものです。開発用アプリは一度ビルドしてインストールすれば、すべてのアプリで共有されます。再インストールされるのは、開発用アプリ自体が変わったとき(Asuka の新しいバージョン)だけです。Go のコードと開発用アプリはソケットで通信します。

保存すると、Asuka は Go のコードを手元のコンピューター向けにビルドし直し、再起動します。かかる時間は 1 秒ほどです。新しいプログラムは、前のプログラムがまだ開発用アプリに応答している間に起動します。前のプログラムが停止すると開発用アプリが再接続し、新しいコードの画面を表示します。何もインストールされません。

--no-hot か --once を付けた場合、アプリがプラットフォーム向けのコードを持つネイティブパッケージ(Package.swift や android/。ネイティブコードを参照)を使っている場合、または開発用アプリでは代わりが務まらない機能を使っている場合は、Asuka は開発用アプリの代わりにアプリそのものをビルドします。リリースビルドと同じく Go のコードを組み込み、アプリ自身の名前と ID を使います。どの理由によるものかは、ターミナルに表示されます。開発用アプリで代わりが務まらない機能は次のとおりです。

  • iOS の asuka/sdk/purchase:App Store は、アプリの商品をそのアプリにしか販売しません。
  • iOS の push.Rewrite:開発用アプリには、プッシュ通知を書き換える通知サービス拡張機能がありません。
  • iPhone の実機での、asuka.json の nfc.iso7816 や nfc.felica:iOS は、アプリ自身の Info.plist に書かれたカードしか探しません。
  • スマートフォンの実機での、ファイルを受け取る shareTarget:共有されたファイルはスマートフォンの中にあり、Go のコードが動くコンピューターにはありません(iOS シミュレーターのファイルは Mac のものなので、シミュレーターではホットリロードモードのままです)。

アプリそのものをビルドする場合:

  • iOS:ネイティブパッケージの Swift を含むホストは、最初に一度ビルドされ、その後は変更があったときだけビルドし直されます。保存するたびに Go のコードをビルドし直してアプリをリンクし、シミュレーター向けに署名して、xcrun simctl で再インストールします。
  • Android:Gradle がアプリを Kotlin のコードとともにビルドします。ビルドは、ホスト、Kotlin のコード、マニフェストのバージョンの組み合わせごとに一度だけです。保存するたびに Go のライブラリをビルドし直し、adb でアプリのプライベートファイルにプッシュして、アプリを再起動します。デバッグ版のアプリは、そこからライブラリを読み込みます。

開発用アプリは、Asuka のどのパッケージが必要とする権限もすべて持っており、利用目的の説明文も開発用アプリ独自のものです。asuka.json でアプリ自身に与えるもの(名前、ID、URL の scheme、links、ios.usage、Android の NFC タグのフィルター)は、ビルドしたアプリにしか反映されません。アプリがこれらを設定していると、asuka dev がそのことを表示します。これらを試すときは --no-hot を使ってください。accent の色は、ホットリロードモードでも反映されます。

リロードで保たれるもの

リロードしても、アプリのシグナルの値は保たれます。そのためのコードを書く必要はなく、ホットリロードモード、デスクトップ、ブラウザーのどれでも同じです。アプリは停止するときに、環境変数 ASUKA_STATE が指すファイルに値を保存します(asuka dev はこれを、Asuka のキャッシュ内にあるアプリのディレクトリの state.json に設定します)。次に実行されたときは、同じ場所で作られたシグナルにそれぞれの値を戻します。同じ場所とは、呼び出しの経路(行番号ではなく、シグナルが作られた関数)、名前、型がすべて同じであることを指します。

場所の名前は、Asuka がビルド時に読み込むソースコードから付けられます。クロージャーは関数の中での位置づけ(代入先の変数やフィールド、渡し先の呼び出し、go や defer)で、シグナルは代入先の変数やフィールドで名前が付きます。そのため、シグナルの周りを編集しても値は失われません。たとえば、シグナルを作るクロージャーの前にハンドラーを追加したり、シグナルの前に同じ型の別のシグナルを追加したりしてもかまいません。

Go
func Counter() ui.Node {
	onReset := func() { ... }  // 追加:count の値は保たれる
	step := signal.New(1)      // 追加:同じく保たれる
	count := signal.New(0)
	...
}

保たれないものは次のとおりです。

  • ナビゲーション:リロードすると、ルートの画面から始まります。
  • Asuka 自身が作るシグナルと、JSON で表せない値(関数、エクスポートされていないフィールド)。
  • 型が変わったシグナル。
  • //asuka:reset コメントのあるファイルで作られたシグナル(そのファイルを編集した場合)。変更するたびに最初からやり直したい画面は、このコメントのあるファイルに置いてください。
  • 名前を変えたシグナルや、別の関数・クロージャーに移したシグナルは、最初からやり直しになります。一つの場所で同じ名前に対して複数のシグナルを作った場合(ループの中など)は、作られた順番で区別されます。

ビルドしたスマートフォンアプリ(--no-hot、--once、ネイティブコード)は、リロードのたびにまっさらな状態から起動し直します。

Go のコードが書き込むファイル(asuka/sdk/store、asuka/sdk/sqlite)は、デスクトップと同じく、ホットリロードモードでも手元のコンピューターに保存されます。store.Dir() は Asuka のキャッシュ内にあるアプリ専用の data ディレクトリで、2 台のデバイスがそれぞれ自分のデータを持つのと同じように、プラットフォームごとに分かれています。データのない状態からアプリを始め直したいときは、このディレクトリを削除してください。ブラウザーでは、これらのファイルはページのもの(IndexedDB)になります。Web を参照してください。

リロード

Asuka は、アプリのディレクトリとそのモジュールのディレクトリを監視し、システムからの変更通知(FSEvents、inotify、ReadDirectoryChangesW。使えない場合はポーリング)で次のファイルの変更を検知します。

  • Go のファイルと go.mod(_test.go は除く)
  • Swift と Kotlin のファイル
  • 言語のカタログ
  • assets/ 以下のファイル
  • --platform web の場合は、アプリのスクリプト(web/*.js)と、Asuka のリポジトリをチェックアウトしているなら Web ホストのスクリプト(platform/web/src)。これらが変わると、ビルドせずにページがリロードされます。

. で始まるディレクトリと、testdata、build、node_modules は監視の対象外です。ビルドが失敗するとエラーを表示し、実行中のアプリはそのまま残ります。次に保存したときに、もう一度ビルドを試みます。

asuka.json のフィールドは、asuka dev の起動時に読み込まれます。変更したら、asuka dev をいったん止めてから実行し直してください。変わった部分がビルドし直され、再インストールされます。

ネイティブコードがリロードされるのは、ビルドしたスマートフォンアプリの場合だけです。この場合、保存のたびに Asuka がビルドと再インストールを行います。Kotlin が変わると Gradle が再実行されるので、Go の変更より時間がかかります。デスクトップアプリの Go で書いたネイティブコードは、ほかのコードと同じようにリロードされます。Mac アプリのネイティブパッケージの Swift は、変更されるとビルドし直され(1、2 秒かかります)、シグナルの値を保ったままアプリが再実行されます。

デバイス

手元のコンピューター:Mac、PC、Linux で、asuka dev を何も指定せずに実行します。

iOS シミュレーターは Mac とネットワークを共有するので、開発用アプリは asuka dev に直接接続できます。

Android エミュレーターからは、手元のコンピューターに 10.0.2.2 でアクセスでき、開発用アプリはそこに接続します。

USB で接続した Android の実機:開発者向けオプションで USB デバッグを有効にし、ケーブルで接続して確認ダイアログで許可したら、adb devices に表示されることを確かめます。asuka dev --platform android は、実機かエミュレーターかを問わず、adb が最初に挙げるデバイスを使います。複数ある場合は、--device にシリアル番号を指定して選んでください。実機は adb reverse を通じて手元のコンピューターにアクセスします。Asuka はポート 7461(アプリ)と 7462(アセット)について、これを設定します。開発中、Asuka は 64 ビットの実機とエミュレーター(arm64-v8a、x86_64)向けにビルドします。

iPhone の実機:asuka dev はシミュレーターでしか動きません。iPhone の実機で動かすには、その iPhone 向けに署名したビルドを asuka build --platform ios --distribution development で作り(配布を参照)、Xcode の「Devices」ウィンドウで .ipa をインストールします。

スマートフォンのブラウザー:asuka dev --platform web --lan でアプリをネットワーク上に配信すると、開くべきアドレスが表示されます。

アセット

デスクトップでは、アプリは assets/ をその場で読みます。ホットリロードモードでは、アプリの assets/ ディレクトリは手元のコンピューターに置いたままです。Go のプロセスがそれを HTTP で配信し、開発用アプリは ui.Image に指定したアセットのパスや assets/fonts のフォントを、そこから読み込みます。ブラウザーでは、開発サーバーがアセットを配信します。アセットを変更すると、アプリがリロードされます。

ビルドしたスマートフォンアプリは、アセットをアプリの中に持ちます。iOS ではビルドのたびにアプリにコピーされ、Android では変更があったときにアプリのプライベートファイルにプッシュされます。

ログ

  • デスクトップとホットリロードモードでは、Go のプロセスの出力(fmt.Println、log、panic)がターミナルに表示されます。
  • ブラウザーでは、アプリの出力に加えて、ページのエラーもターミナルに表示されます。
  • iOS のビルドしたアプリ:アプリの出力(Go の出力と Swift の print)は、シミュレーターのコンソールに出ます。
  • Android のビルドしたアプリ:アプリの logcat に出ます。Android はプロセスの標準出力を捨ててしまうため、Go のコードの出力は asuka というタグを付けて logcat に送られます。
  • スマートフォンで --once を使った場合は、何も表示されません。「コンソール」アプリ(Console.app)か adb logcat で確認してください。

アプリをバックグラウンドにしたとき

ホットリロードモードでアプリから一時的に離れても(カメラアプリで写真を撮る、ブラウザーでサインインするなど)、セッションは終わりません。バックグラウンドではシステムが開発用アプリを停止させることがあるため、開発用アプリがバックグラウンドにある間、Go のコードは開発用アプリからの応答を通常の 10 秒ではなく最大 10 分まで待ちます。Android がバックグラウンドのアプリのネットワークを切断した場合、セッションは 3 分間保持されます。その間に開発用アプリが戻ってくれば、画面もそのままの状態でセッションを再開します。それより後に戻ってきた場合は、アプリをルートの画面から始め直します。

asuka dev を止める(Ctrl-C)と、開発用アプリも停止します。次のセッション(別のアプリのセッションかもしれません)に開発用アプリが加わらないようにするためです。デスクトップでは、アプリが停止します。

asuka vet

Shell
asuka vet [flags] [packages]

asuka vet は、ビルドも実行もできるのに意図どおりに動かない間違いを見つけます。報告するのは次のものです。

  • reads:コンポーネントがビューを組み立てている間に読まれたシグナル。その表示は変わらなくなります。たとえば、コンポーネントの本体に書いた ui.StaticText(fmt.Sprint(count.Get())) や if open.Get() { ... } です。何が更新されるかを参照してください。
  • mainthread:ui.Post の中ではなくゴルーチンで書き込まれたシグナル。メインスレッドを参照してください。
  • memowrites:メモの中で書き込まれたシグナル。これは panic します。
Text
$ asuka vet
main.go:14:23: count.Get() is read once, while App builds its views, so the constant made from it never changes: give the prop signal.Func(func() T { ... }) instead, or use count.Peek() to read it once on purpose

asuka vet は確実なものだけを報告するので、報告されたものは直す価値があります。各メッセージには、修正方法と、意図したものであることを示す方法が書かれています。意図して一度だけ読む場合は Peek(アクセサなら signal.Untrack)を使えば、報告されることはありません。

パッケージはデフォルトで ./... で、go vet と同じ形で指定します。フラグ(-tags=...、-json)も同じで、さらにチェックごとに一つずつフラグがあります。たとえば -reads=false で reads のチェックを無効にできます。何か報告すると asuka vet は終了コード 1 で終了するので、CI で使えます。実行するチェックは go vet -vettool=$(which asuka) と同じです。この形で実行した場合、go vet 自身のチェックは実行されません。

asuka dev は起動時と保存のたびに、asuka vet をバックグラウンドで実行し、新しく見つかったものを警告として表示します。asuka build は、ビルドの前に実行します。どちらも、何か見つかっても処理を止めることはありません。所要時間はビルドと並行して 1、2 秒ほどです。--no-vet を付ければ、どちらでも無効にできます。

スナップショットとスクリプト

デスクトップアプリは、いくつかの環境変数を受け付けます。これらを使うと、アプリに特定の状態を表示させてキャプチャーできるので、変更を目で確かめたり、CI で確認したりするのに便利です。アプリのディレクトリで go run . を使って実行します。

Shell
ASUKA_SNAPSHOT=shot.png go run .                  # ウィンドウが表示されたら PNG に保存して終了
ASUKA_SNAPSHOT=shot.png ASUKA_DUMP=1 go run .      # あわせてビューツリーを shot.png.txt に書き出す
ASUKA_SCRIPT="tap:Add;tap:Add" ASUKA_SNAPSHOT=shot.png go run .   # Windows、Linux:先に操作を実行する
  • ASUKA_SNAPSHOT:ウィンドウが表示されたら(スクリプトがあればその実行後に)、ウィンドウを PNG に書き出して終了します。ASUKA_SNAPSHOT_STAY=1 を指定すると、終了せずにアプリを実行し続けます。
  • ASUKA_DUMP:その PNG の隣にビューツリーを書き出します。各ビューの種類、フレーム、表示内容が含まれます。
  • ASUKA_SCRIPT(Windows と Linux):スナップショットの前に、; で区切った操作を順に実行します。使える操作は、tap:<text>(そのテキストを表示しているビュー。ボタンのタイトル、テキスト、タブなど)、type:<text>(フォーカスのある入力欄に入力)、back、key:<shortcut>(ctrl+shift+k)、menu:<item>(メニューバーの項目)、escape、close(使用中のウィンドウを閉じる)、wait:<duration>(500ms)、snap:<file> です。ホストごとに追加の操作もいくつかあります。それぞれの automation.go を参照してください。

よくある問題

  • no iOS simulator SDK (install Xcode):Xcode をインストールして一度起動し、Xcode の設定の「Components」で iOS シミュレーターのランタイムを追加します。no iPhone simulator; add one in Xcode や no simulator "..." は、その名前のシミュレーターがないという意味です。シミュレーターの一覧は xcrun simctl list devices で確認できます。
  • no Android SDK: install Android Studio or set ANDROID_HOME:Asuka は ANDROID_HOME、ANDROID_SDK_ROOT の順に探し、次に Mac なら ~/Library/Android/sdk、Linux なら ~/Android/Sdk を探します。
  • no Android NDK:Android Studio の SDK Manager(SDK Tools、NDK)から NDK をインストールします。
  • no Android device or emulator:Android Studio の Device Manager で AVD を作るか、実機を接続します。Asuka が起動したエミュレーターについては、起動が完了するまで最大 4 分待ちます(the emulator didn't boot)。
  • Java:Gradle には JDK が必要です。JAVA_HOME が使われ、なければ Android Studio に同梱の JDK が使われます。
  • Android の初回ビルドが遅い:初回だけ、Gradle が自分自身と Android のライブラリをダウンロードするためです。
  • the player connected ... and hasn't shown the app:開発用アプリは Go のコードに接続できたものの、メインスレッドが止まってしまい、アプリを表示できていない状態です。Asuka は開発用アプリのゴルーチンを、Asuka のキャッシュ内にあるアプリのディレクトリの player-stacks.txt に保存します(パスが表示されます)。バグを報告するときは、このファイルを添付してください。開発用アプリを起動し直すか、--no-hot を付けて、Go のコードを組み込んだアプリをビルドして実行してください。
  • 実機にリリースビルドが入っている:asuka build でインストールしたアプリ(署名鍵が異なります)は、デバッグ版をインストールするために、データごとアンインストールされます。
  • asuka dev --platform windows runs the app on this PC:asuka dev で Windows のアプリを実行できるのは、Windows 上だけです。ほかのシステムでは asuka build --platform windows でビルドし、できたフォルダーを PC にコピーしてください。
  • Linux での ... isn't installed:ホストには GTK 4.14 以降と libadwaita 1.5 以降が必要です(the Linux host needs GTK 4.14 and libadwaita 1.5 or later)。また、パッケージによっては独自のライブラリが必要です。ui.WebView には WebKitGTK 6.0、動画とオーディオには GStreamer、asuka/sdk/secure には libsecret が必要です。足りないライブラリはエラーメッセージに示されるので、ディストリビューションのパッケージマネージャーでインストールしてください。
  • Linux のアプリが開かない:アプリにはディスプレイが必要です。SSH 経由や CI では Xvfb(xvfb-run -a)の下で実行し、D-Bus を使う機能のためにセッションバス(dbus-run-session)も用意してください。
  • CLI とアプリの Asuka:CLI はホストの Swift と Kotlin のコードを内蔵しており、Asuka の Go コードのバージョンはアプリの go.mod で決まります。CLI は、アプリが要求するバージョンのものをインストールしてください。
  • 最初からやり直す:Asuka のビルド、キャッシュ、保存された状態、アプリの開発用データは、Asuka のキャッシュディレクトリにあります。場所は Mac では ~/Library/Caches/asuka、Linux では ~/.cache/asuka、Windows では %LocalAppData%\asuka です。このディレクトリを削除すると、状態もデータもない状態から、すべてをビルドし直せます。

テスト

デバイスやディスプレイを使わずに、go test でアプリの UI をテストする方法はテストを参照してください。Asuka 自体のリポジトリでは、各ホストをプラットフォームの実際のビューを使ってテストしています。

Shell
go test ./...                                      # コア(host/fake を相手に)
go test ./host/darwin                              # macOS:実際の AppKit のビュー
go test ./host/windows                             # Windows:実際のウィンドウ
xvfb-run -a dbus-run-session go test ./host/linux  # Linux:実際の GTK ウィジェット
swift test --package-path platform/ios             # Swift ホストのデコーダー
node --test platform/web                           # Web ホストのデコーダー

go test ./host/darwin -run Conformance と go test ./host/windows -run Conformance は、あるスタイルに対してホストが描画するもの(辺、角、枠線、影、グラデーション)を、ウィンドウのキャプチャー上で計測して確かめます。後述の e2e テストは、シミュレーター、エミュレーター、ヘッドレス Chrome で同じことを行います。

GUI テスト

e2e/ では、デバッグ版のホストが持つ自動操作の機能を使って、各プラットフォームでサンプルアプリを動かします。

Shell
go test -tags e2e ./e2e -run IOS       # 起動中のシミュレーター、なければ最近の iPhone
go test -tags e2e ./e2e -run Android   # asuka dev が選ぶデバイス
go test -tags e2e ./e2e -run Web       # ヘッドレス Chrome(ASUKA_CHROME:そのパス)
xvfb-run -a dbus-run-session go test -tags e2e ./e2e -run Linux  # GTK 4、Linux 上で

各テストは asuka dev --once でサンプルをビルドし(Go のコードを組み込んだデバッグビルド)、タップ、入力、画面の読み取りを行います。操作の手段は、iOS では通知、Android ではブロードキャスト、Web では開発サーバー(platform/web/src/automation.js)、Linux では ASUKA_COMMANDS が指すディレクトリ内のファイル(host/linux/automation.go)です。テストがあるのは counter、notes、chat、navigation、languages の各サンプルで、どのプラットフォームでも内容は同じです。テストは決まった時間だけ待つことはせず、期待した内容が画面に表示されるまで最大 1 分待ちます。

iOS のテストには Xcode(xcrun)が必要です。Android のテストには、PATH 上の adb と、asuka dev --platform android が必要とする SDK と NDK が必要です。Linux のテストには、ディスプレイ(CI では Xvfb のもの)とセッションバスが必要です。これらがない場合、テストはスキップされます。macOS と Windows にはまだ e2e テストがありません。代わりに、ホストのテストが実際のウィンドウを操作します。

ベンチマーク

bench/ では、Asuka を SwiftUI や Jetpack Compose と比較します。同じアプリをそれぞれで書き、リリースビルドでフレームごとに時間を計測します。

Shell
go run ./bench/run -ios        # シミュレーターで asuka と SwiftUI を比較
go run ./bench/run -android    # 起動中のエミュレーターまたはデバイスで asuka と Compose を比較

-runs で、それぞれの実行回数を指定します(デフォルトは 5 回)。中央値は bench/results に保存されます。シナリオ、計測方法、最新の結果は bench/README.md にまとめています。

asuka.json

asuka dev と asuka build は、同じ asuka.json を読み込みます。キーについては asuka.json のリファレンスを、リリースに向けてそれらがどう組み合わさるかについては配布を参照してください。