GitHub Actionsでthree.jsの単体テスト


はじめに

この記事は、GitHub Actions上でthree.jsの単体テスト方法を記録、共有するためのものです。

node.jsでWebGLを利用したコードを単体テストし、GitHub Actions上で処理します。ヘッドレスブラウザやelectronは使用しません。

想定する読者

この記事は、以下の読者を想定して書かれています。

  • JavaScriptの開発経験がある
  • Jestを使ったことがある
  • three.jsを使ったことがある
  • リポジトリをGitHubで管理している / 管理したい

JavaScriptの解説やJestのインストールガイドはこの記事には含まれません。

想定する環境

この記事は、以下の環境を想定して書かれています。記事を読む前に、お手元の環境をご確認ください。

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x, 14.x, 16.x]

GitHub Actions上の仮想環境はubuntu-latest、node.jsのバージョンは12から16までを想定しています。

また、テスティングフレームワークはJestを利用します。

"devDependencies": {
  "jest": "^27.0.4",
}

先に結論だけ

GitHub Actionsでthree.jsを単体テストする最小限のリポジトリを作りました。

WebGLを利用した単体テスト

Jestはnode.jsで稼働するJavaScriptテスティングフレームワークです。Jestにはjsdomという、JavaScriptで記述されたDOM実装が含まれています。このjsdomを使ってCanvasを作成し、WebGLを利用するコードをテストします。

Jestでjsdomを利用する

Jestでjsdomを利用するには、設定ファイルを作成し環境にjsdomを指定します。

▼jest.config.js

module.exports = {
  testEnvironment: "jsdom",
};

もしくは、jestコマンドに--env=jsdomオプションを追加します。

▼package.json

"scripts": {
  "test": "jest --env=jsdom"
}

課題

jsdomは軽量化のため、標準設定ではCanvas要素をdiv要素で代用します。そのため、CanvasRenderingContext2DやWebGLRenderingContextはテストで使えません。

  • Canvas要素のサポート
  • WebGLRenderingContextのサポート

jsdomでWebGLをテストするためには、この2つの課題を解決しなければなりません。

Canvas要素のサポート

jsdomでCanvas要素をサポートするために、node-canvasを利用します。

npm install canvas --save-dev

jsdomはnode-canvasのインストールを自動で検知します。ユーザーがなにか設定をする必要はありません。

const canvas = document.createElement("canvas");

テストコード上でdocument.createElementを呼び出すと、node-canvasからCanvas要素が返されます。

WebGLRenderingContextのサポート

jsdomでWebGLRenderingContextをサポートするために、headless-glを利用します。headless-glはnode.js上でWebGLRenderingContextを作成するモジュールです。

npm install gl --save-dev

テストコードでは、以下のようにWebGLRenderingContextを生成します。

const gl = require('gl')(width, height)

width, heightには好きなサイズを指定できますが、THREE.WebGLRendererのsetSize関数で変更されてしまうため、最小限のサイズを指定します。

const gl = require('gl')(1, 1)

テストコードの作成 

node-canvasheadless-glを使ってテストを作成します。

▼./__test__/Test.spec.js

const THREE = require("three");

jest.spyOn(console, "error").mockImplementation()

describe("Test", () => {
    const canvas = document.createElement("canvas");

    test("2d context should be exist", () => {
        const context = canvas.getContext("2d");
        expect(context).toBeTruthy();
    })

    test("generate webgl context", ()=>{
        const gl = require("gl")(1,1);
        expect(gl).toBeTruthy();
    })

    test("generate webgl renderer", ()=>{
        const gl = require("gl")(1,1);
        const renderer = new THREE.WebGLRenderer({context:gl, canvas:canvas});
        renderer.setSize(640, 480);

        expect(renderer.getContext()).toBeTruthy();
        expect(renderer.getContext()).toBe(gl);
        expect(renderer.domElement.width).toBe(640);
    })
})

このテストで、以下の課題2点が確認できました。

  • Canvas要素のサポート
  • WebGLコンテキストのサポート

また、CanvasとWebGLコンテキストを利用してWebGLRendererが生成できました。

GitHub Actionsで実行する

ここまでで作成したテストはそのままではGitHub Actionsで動作しません。GitHub Actionsの仮想環境ubuntu-latestには、headless-glが依存するいくつかのパッケージが含まれていないためです。

解決方法1 : macos-latestかwindows-latestを使う

GitHub Actionsがサポートする仮想環境一覧

この問題のもっとも簡単な解決方法は、仮想環境のmacos-latestwindows-latestへの変更です。この2つの仮想環境は、headless-glの動作に必要なパッケージをすべて含んでいます。

jobs:
  build:
    runs-on: macos-latest

ただしmacos-latestwindows-latestはLinuxにくらべ、それぞれ2倍と10倍のコストがかかります。

公式ドキュメント : GitHub Actionsの支払いについて

パブリックリポジトリでは、GitHub Actionsの費用は利用者に請求されません。しかしサーバー資源の浪費は避けるべきです。

解決方法2 : 動作に必要なパッケージをインストールする

GitHub Actionsのstepsでは、sudoコマンドが使えます。必要なパッケージをインストールすれば、ubuntu-latestでheadless-glが動作します。

headless-glに必要なパッケージは以下の通りです。

  • libxi-dev
  • xvfb
  • libgl1-mesa-dev

このパッケージをインストールし、テスト実行前に仮想フレームバッファーXvfbを起動します。

    steps:
    - uses: actions/checkout@v2
    # ここでパッケージをインストール
    - name: install apt packages for headless-gl
      run: sudo apt-get install libxi-dev xvfb libgl1-mesa-dev
    # ...中略...
    # npm testの実行直前に xvfb-run --auto-servernum で仮想フレームバッファーXvfbを起動
    - run: xvfb-run --auto-servernum npm test

参考 : Usage on GitHub Actions with xvfb

これでheadless-glが動作します。

参考記事

以上、ありがとうございました。