Melchior

バンクーバーで働くエンジニアの備忘録

Unity Editor上でのVersion Controlの使い方 〜Perforce編〜

概要

UnityにはBuilt-inのVersionControlという関数があり、これを利用することでシェルコマンドを叩くこと無くGitやPerforceなどにCommit・Submitすることができます。

Unity Editor上からファイルを自動生成・削除、及びPerfoceに反映させるツールを作ったのでその一部を紹介します。

Version Controlの設定

[Project Setitngs] > [Editor]上にVersion Controlという項目を設定します。今回はPerfoceを使用するためPerfoceの設定をしました。

f:id:suguru03:20190623052840p:plain

Connectのボタンを押してConnectedになっていることを確認してください。

VersionControlクラスの使い方

まずUnityEditor.VersionControlをインポートします、C#ではusingを使用します。

using UnityEditor.VersionControl;

また各関数でassetsのパスが必要となるため、assetsを用意しておきます。

var assetPath = Application.dataPath;
var assets = new AssetList
{
    new Asset(assetPath)
};

Task

今回使用する関数はどれもUnityEditor.VersionControl.Taskを返します。必要に応じでWaitを呼んでください。 このTaskはawaitableではないため同期処理になりプロセスを専有します。他の作業がブロックされるのでなるべく呼ぶ回数を減らすことをおすすめします。

Checkout

Perfoceではすべてのファイルがreadonlyなので、ファイルを変更する前に必ずcheckoutする必要があります。 そのため必ずWaitを呼ぶ必要があります。

Provider.Checkout(assets, CheckoutMode.Asset).Wait();

PerfoceにはChaneglistという概念があります。 Gitのbranchに似てますが、同時に複数のChangelistを使用することができます。

例えば作業AとBを同時にする必要があり、それぞれを別のChangelistで管理することでSubmitするタイミングを調整することができます。

ここでCheckoutされたファイルは自動的にdefaultのChangelistに入ります。

Add

新しいファイルを追加します。ディレクトリパスを指定することでディレクトリ配下の新規ファイルを追加することができます。

Provider.Add(assets, true).Wait();

これらのファイルはAddコマンドを呼ばない限りPerfoce上で確認することができません。

これがとても厄介で、追加漏れしたファイルは存在しているにも関わらず、Perfoce上で確認ができないため困ることが多々あります。 この追加漏れをチェックするreconcileというコマンドがありますが、かなり不便です。

Delete

既存のファイルを削除します。

Provider.Delete(assets).Wait();

Revert

変更を破棄します。 Perforceの場合、チェックアウトされたすべてのファイルがSubmitの対象になります。そのため変更されてないファイルはUnchangedで破棄する必要があります。

Provider.Revert(assets, RevertMode.Normal).Wait();
Provider.Revert(assets, RevertMode.Unchanged).Wait(); // 未変更のファイルのみを破棄

Submit

変更を反映します。 changeSetのdescriptionはChangelistのdescriptionにリンクします。第四引数はSaveOnlyです。

var changeSet = GetChangeSet();
Provider.Submit(changeSet, assets, changeSet.description, true).Wait();

Submitを2回呼ぶとコネクションが切れることがあるので、2回呼ぶことは避けたほうが良さそうです。 コネクションが切れてしまった場合はProject Settingsからまた有効にする必要があります。

Example

実際のコードではasset配下は範囲が広すぎてパフォーマンス上の懸念があるため、作業範囲を限定します。 この例は、Zipファイルを展開し、Zip内に存在するファイルの追加・変更と存在しないファイルの削除をResources配下に反映します。

// Zipファイルの展開
using (var archive = new ZipArchive(stream, ZipArchiveMode.Read, true))
{
  var assetPath = Application.dataPath;
  var resourcePath = Path.Combine(assetPath, "Resources");

  var assets = new AssetList
  {
      new Asset(resourcePath)
  };

  // Resource配下をCheckout
  Provider.Checkout(assets, CheckoutMode.Asset).Wait();

  // Zipファイル内に存在するパスのキャッシュ
  var assetPathSet = new HashSet<string>();
  foreach (var entry in archive.Entries)
  {
      var destinationPath = Path.Combine(resourcePath, entry.FullName);
      var dirName = Path.GetDirectoryName(destinationPath);
      if (dirName != null)
      {
          // ディレクトリが存在しない場合エラーになるので作成する
          Directory.CreateDirectory(dirName);
      }
      entry.ExtractToFile(destinationPath, true);
      // フルパスではなくasset配下のパスが必要なためassetのパスを取得する。これはmetaファイルにも使用される。
      assetPathSet.Add(new Asset(destinationPath).assetPath);
  }

  // ディレクトリのmeta情報のために追加する
  assetPathSet.Add(new Asset(resourcePath).assetPath);

  // ディレクトリ配下の新規ファイルを追加する
  var addTask = Provider.Add(assets, true);
  addTask.Wait();

  // Zipファイルに存在しないファイルの削除
  var deleteAssets = new AssetList();
  // addTask.assetListはチェックアウトされているすべてのファイル名を取得できる
  deleteAssets.AddRange(addTask.assetList.Where(asset => !assetPathSet.Contains(asset.assetPath)));
  Provider.Delete(deleteAssets).Wait();

  // これらのファイルをデータ配下のChangelistに移動させる
  var changeSet = GetChangeSet("データ");
  Provider.Submit(changeSet, addTask.assetList, changeSet.description, true).Wait();

  // 未変更のファイルを取り消す
  Provider.Revert(assets, RevertMode.Unchanged).Wait();
}

まとめ

Unity EditorのVersionControlの使い方について紹介しました。 この順番で呼ばないとうまく動かなかったりハマりどころが多かったりしますが、自動化すると便利なことは多いのでぜひトライしてみてください。

Gitを使いたい。

リンク

FlatBuffers導入

概要

FlatBuffersを導入手順についての記事です。

FlatBufferとは

FlatBuffersとはGoogle社が開発しているクロスプラットフォーム対応のシリアライゼーションライブラリです。主にゲームで使われることを目的として開発されています。Protocol Bufferに似ていますが、大きな違いはバイナリデータをパースせずにシリアライズされたデータを使用するため、高速かつメモリを効率的に使うことができます。

Flatbuffersのインストール

はじめにflatbuffersbrewでインストールします。 brewが使えない場合はBuildingのページを参考にインストールすることができます。

brew install flatbuffers

flatcというコマンドが使えるようになれば準備完了です。

FBSファイルの作成

次にFBSファイルを作成します。FBSファイルとはFlatbuffers用のスキーマ定義ファイルです。これによりバイナリファイルの作成および各言語のparserを生成することができます。

Tutorialを参考にFBSファイルを作成します。

// monster.fbs

// Example IDL file for our monster's schema.
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}
table Weapon {
  name:string;
  damage:short;
}
root_type Monster;

バイナリファイルの作成

バイナリファイルはJSONまたはJSON5のフォーマットを使用します。今回はJSON5を使用してバイナリファイルを生成します。

// monsterdata.json5

{
  pos: {
    x: 1,
    y: 2,
    z: 3
  },
  hp: 300,
  name: 'Orc'
}

先程生成したFBSファイルを使用してJSONデータをバイナリファイルに変換します。

flatc -b monster.fbs monsterdata.json5

monsterdata.binが生成されればOKです。

JavaScriptで読み込み

今回はJavaScriptファイルをFBSファイルより自動生成し、実際にNode.jsでバイナリファイルを読み込みます。

flatc --js monster.fbs

monster_generated.jsファイルが生成され、これを使用して実際にバイナリファイルを読み込んでみます。

Nodeの実行環境をTutorialを参考に作成し実行し、ログが出れば成功です。

yarn init -y
yarn add flatbuffers
// example.js

const fs = require('fs');
const path = require('path');
const { flatbuffers } = require('flatbuffers');

const { MyGame } = require('./monster_generated');

const filepath = path.resolve(__dirname, 'monsterdata.bin');
const data = new Uint8Array(fs.readFileSync(filepath));

const buf = new flatbuffers.ByteBuffer(data);
const monster = MyGame.Sample.Monster.getRootAsMonster(buf);
console.log(monster.hp()); // 300
console.log(monster.name()); // Orc
node example.js

まとめ

FlabBuffersの導入をしました。まだベンチマークを計測していないですが、C#クライアントではうまく動いてより良い感じです。またdefault valueを使用することでデータ量を減らすこともできるようなので、ぜひ使っていきたいと思います。

Link

Serverless + AWS Lambda + API Gatewayのヘッダマッピング

概要

AWS Lambda上でリクエストヘッダの取得とレスポンスヘッダの追加をServerless上で設定する方法がわからなくハマったので、それについてまとめました。

リクエストヘッダのマッピング

AWS Lambdaで生のリクエストヘッダを取得するためには、API Gateway上でマッピングテンプレートによりマッピングする必要があります。今回はいくつかのエンドポイントでヘッダ情報をチェックする必要があったため、こちらのドキュメントを参考に以下のように追記しました。

functions:
  hello:
    handler: header.hello
    events:
      - http:
          path: hello
          method: GET
          request: ${self:custom.request}
custom:
  request:
    template:
      application/json:
        method: $context.httpMethod,
        body : $input.json('$'),
        headers:
          #foreach($param in $input.params().header.keySet())
          $param: $util.escapeJavaScript($input.params().header.get($param))
          #if($foreach.hasNext),#end
          #end

これでヘッダ情報が取得できるようになります。

export const hello = async (event, context) => {
  cosnole.log(event.headers);
);

レスポンスヘッダのマッピング

今回、AWS Gatewayのデフォルトはapplication/jsonのようで、これをapplication/json; charset=utf-8に書き換える必要がありました。yaml上で解決したかったのですがうまく解決できず。今回はこちらを参考にコードに追記して解決しました。

export const hello = async (event, context) => ({
  statusCode: 200,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
  },
  body: JSON.stringify({
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  }),
});

共通関数の作成

以下の目的のため、共通の関数を作成しました、ソースコードこちらです。

  • 冗長なコードを減らす
  • リクエストヘッダのチェック
  • レスポンスヘッダに上記の情報を追記する
  • Serverlessの関数はasync関数を取らないといけないが、もっと自由に定義したい
  • 型推論を利用したい

header.ts -> src/funcitons/example.tsに移動し、serverless.yamlの記述を書き換えます。

functions:
  hello:
    handler: src/functions/example.hello
    events:
      - http:
          path: hello
          method: GET

ディレクトリ構造は以下のような感じです。

|--package.json
|--serverless.yml
|--src
|  |--errors.ts
|  |--functions
|  |  |--example.ts
|  |  |--util.ts
|--tsconfig.json
|--yarn.lock

次にaws-lambdaの型定義を追加します。

$ yarn add -D @types/aws-lambda

共通関数は以下のように書きました。

import { Handler, Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

import { CustomError } from '../errors';

interface HandlerResult {
  statusCode?: number; // defaultは200
  body?: string | object; // JSON.stringifyするのでなんでもOK
}

type CustomHandler = (
  event: APIGatewayProxyEvent,
  context: Context,
) => void | HandlerResult | Promise<HandlerResult>;

export const createAPIGatewayHandler = (handler: CustomHandler): Handler => async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  try {
    // check headers

    const { statusCode = 200, body = '' } = (await handler(event, context)) || {};
    return createResponse({
      statusCode,
      body: JSON.stringify(body), // JSON.stringify(undefined)にならないようにbodyを初期化
    });
  } catch (error) {
    let { statusCode, message } = error;
    if (!(error instanceof CustomError)) {
      // unknownエラーは500エラー
      statusCode = 500;
      message = 'Internal Server Error';
    }
    return createResponse({
      statusCode,
      body: JSON.stringify({ message }),
    });
  }
};

const createResponse = (response: APIGatewayProxyResult): APIGatewayProxyResult => ({
  ...response,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    ...response.headers,
  },
});

example.tsは以下のようになり、event, contextともに型推論が利くので型定義は不要です。

import { createAPIGatewayHandler } from './util';

export const hello = createAPIGatewayHandler(event => ({
  body: {
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  },
}));

まとめ

Lambda上でリクエストヘッダ情報のチェック・レスポンスヘッダの書き換えができるようになりました。認証のためのCustom Authorizersという機能もあるのでこちらもまたチェックしてみようと思います。

リンク

Serverless + TypeScript + AWS Lambdaの開発環境構築

概要

Serverless FrameworkとはLambda関数などを開発・デプロイするためのツールです。今回はServerless + TypeScript + AWS Lamdbaのローカル開発環境の導入手順について書きます。

プロジェクトの作成

はじめにServerlessの環境構築をします。今回はAWS LamdbaのNode.jsを利用するため、templateはaws-nodejsで作成します。pathのサービス名は適宜変更してください。

$ npx serverless create --template aws-nodejs --path my-service
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.36.0
 -------'

ドキュメントにはnpm install -g serverlessと書かれていますが、npxを使用することをオススメします。

このときのserverlessのバージョンはv1.36.0でした。

次にnpm packageを作成します。Serverlessのライブラリは必須ではないですが、CLIのバージョン固定するためにインストールしておきます。

$ cd my-service

$ yarn init
$ yarn add -D serverless

ローカル開発環境の構築

次にserverless-offlineのライブラリでローカル環境の構築を行います。

$ yarn add -D serverless-offline

serverless.ymlserverless-offlineとテスト用にhelloを以下のように追記しておきます。

plugins:
  - serverless-offline

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: GET

起動して以下のログが出れば成功です。

$ yarn sls offline start
Serverless: Offline listening on http://localhost:3000

package.jsonスクリプトを追加しておくと便利ですね。

curlで叩いてみます。

$ curl http://localhost:3000/hello
{"message":"Go Serverless v1.0! Your function executed successfully!",... }

上記のログが出れば設定完了です。

デプロイ

次にAWS Lambdaへデプロイします。serverless.ymlに以下を追記します。region、profile名を適宜変更してください。

provider:
  name: aws
  runtime: nodejs8.10
  region: us-west-2
  profile: my-service

AWSの認証キーは必要に応じて~/.aws/credentialsに追記します。

[my-service]
aws_access_key_id = <your-key-here>
aws_secret_access_key = <your-secret-key-here>

以下のコマンドでデプロイします。

$ yarn sls deploy

AWSコンソール上に表示されていれば完了です。

TypeScriptの設定

次にTypeScriptの設定を行います。設定はとてもシンプルです。

$ yarn add -D typescript serverless-plugin-typescript

offlineと同様に以下の記述を追記します。

plugins:
  - serverless-offline
  - serverless-plugin-typescript

handler.jshandler.tsに変更し、以下のように書き換え、ローカルサーバが正常に立ち上がれば完了です。

export const hello = async (event, context) => ({
  statusCode: 200,
  body: JSON.stringify({
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  }),
});

ソースコードこちらに上げてあります。

まとめ

まだ使い始めたばかりですが、Serverless FrameworkでAWS Lambdaの開発・テストがローカル環境で行うことができとても便利です、ぜひ試してみてください。

リンク

MetabaseをElastic Beanstalkにデプロイする

概要

Metabaseを導入したので、それの手順についてまとめます。

Metabaseのデプロイ

こちらのリンクからAWSにデプロイするボタンがあるのですが、うまく動かなかったため今回は手動でセットアップします。

まずはじめにElastic Beanstalkのサーバを構築します。プラットフォームは事前設定済みプラットフォームのDockerを選択します。

f:id:suguru03:20181205161604p:plain

次にアプリケーションコードをアップロードします。ソースコードは既にS3上に上がっているためS3のURLを指定します。

https://s3.amazonaws.com/downloads.metabase.com/v0.31.1/metabase-aws-eb.zip

最新バージョンはこちらで確認できます、適宜URLのバージョン部分を書き換えてください。

f:id:suguru03:20181205162405p:plain

f:id:suguru03:20181205165545p:plain

リージョンのエラーが出る場合はソースコードをローカルにダウンロードし、ローカルファイルからダウンロードしたソースコードを選択しアップロードします。 「更にオプションを追加」をクリックし適宜設定し、環境を構築します。

Auroraの設定

今回は既存のAurora上にMetabase用のDBを作成します。新しくDBを作成する場合はこちらを参考にしてください。

ルートユーザでログインし、Aurora上にMetabase用のデータベースとユーザを作成します。

CREATE DATABASE metabase CHARACTER SET utf8mb4;
CREATE USER 'metabase.admin'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON *.* TO 'metabase.admin'@'%' WITH GRANT OPTION;

必要に応じて、対象となるデータベース上にReadonlyユーザを作成してください。

環境変数の設定

スタートガイドに従ってElastic Beanstalk上に環境変数を設定します。上記で生成したユーザを設定してください。

f:id:suguru03:20190105093442p:plain

設定を更新して以下の画面が表示されれば準備完了です。

f:id:suguru03:20190106072554p:plain

まとめ

Metabaseとても便利そうなのでぜひ使ってみてください。

ソース

VM moduleを利用してLambdaのような機能を提供する

概要

Express + VMモジュールを使用し、Lambdaのような機能を提供するサーバの構築方法、またVMの注意点についてまとめます。

vmとは

VMとは同プロセス上で別のNodeスクリプトの実行をサポートするモジュールです。evalに似ていますが、Contextの値を外から代入できる大きな違いがあります。 また同プロセス上で動くので値の共有が可能です。以下が実行例です。

const vm = require('vm');

const code = `() => num + 1`;
const func = vm.runInNewContext(code, { num: 1 });
func(); // 2

このようにVM上で生成された関数をVM外で使用することができます。

サーバ概要

下図がざっくりとしたサーバ構成図です。

f:id:suguru03:20180713132219p:plain

クライアントがリクエストとともにクライアントバージョンを送信し、そのバージョンとマッチしたVMスクリプトをDBから取得します。また一度取得したスクリプトはパフォーマンス向上のためオンメモリに保持しています。

そのスクリプトVM上で起動し、サーバのAPIをContext経由で渡すことでスクリプト上からサーバへのリクエストを可能にします。 以下が実行例です。

// script1.js
handlers.getData = async () => ({
  a: await apis.getDataA(),
  b: await apis.getDataB(),
  c: await apis.getDataC(),
});
// vm.js
const vm = require('vm');
const fs = require('fs');
const path = require('path');

const script = fs.readFileSync(path.join(__dirname, 'script1.js')); // 実際はDBから取得

const apis = {
  async getDataA() {
    return 'a';
  },
  async getDataB() {
    return 'b';
  },
  async getDataC() {
    return 'c';
  },
};

const handlers = {};
const context = {
  apis,
  handlers,
};

vm.runInNewContext(script, context);

(async () => {
  const res = await handlers.getData();
  console.log(res); // { a: 'a', b: 'b', c: 'c' }
})();
$ node vm.js

このようにVM上で作成されたhandlersをVM外で実行することにより、一つのクラスタで複数のバージョンの異なるクライアントをサポートしています。

使用上の注意

Objectの扱い

JavaScriptでは全てのオブジェクトはObject型を継承します。例えばArrayはObject型を継承しているため、以下のようにinstanceoftrueになることが確認できます。

const array = [];
console.log(array instanceof Array); // true
console.log(array instanceof Object); // true

しかしVM上ではVM外とは異なるObject型が生成され、VM内の全てのObject型はVM外とは異なるObject型を継承します

const vm = require('vm');
const code = `() => []`;
const func = vm.runInNewContext(code, {});
const res = func();
console.log(res instanceof Array); // false
console.log(res instanceof Object); // false

逆も同様な問題があります。

const vm = require('vm');

const code = `() => array instanceof Array`;
const func = vm.runInNewContext(code, { array: [] });
func(); // false

Arrayの場合はArray.isArrayObject.prototype.toStringを使用することで、Arrayであるかどうかの判別が可能です。

console.log(Array.isArray(res)); // true
console.log(Object.prototype.toString.call(res)); // [object Array]

しかしVM上で生成された値をサードパーティのライブラリに代入する際、内部でinstandeof等を使用している可能性があるためこの方法が安全とは言えません。

エラーハンドリング

エラーハンドリングでも同様な問題があります。

const vm = require('vm');

const code = `() => { throw new TypeError() }`;
const func = vm.runInNewContext(code, {});

try {
  func();
} catch (e) {
  if (e instanceof TypeError) {
    console.log('type error!');
  } else if (e instanceof Error) {
    console.log('common error!');
  } else {
    console.log('unkown error!');
  }
}
// unknown error!

つまりVM上で生じたTypeErrorinstnaceofでは判別することができません。

util.inheritsを使った解決方法

util.inheritsとは、Node上でprototype関数を継承するために使われる関数です。 現在はextendsシンタックスを使用してclassを継承することが主流ですが、classの実態はprototype関数であり、util.inheritsを使用して拡張することができます。

基本的な使い方は以下の通りです。

const util = require('util');

// prototype function
function MyError1() {}

new MyError1() instanceof Error; // false

util.inherits(MyError1, Error);

new MyError1() instanceof Error; // true

// class
class MyError2 {}

new MyError2() instanceof Error; // false

util.inherits(MyError2, Error);

new MyError2() instanceof Error; // true

これを踏まえVM上のclassの継承関係を解決していきます。以下のように呼び出し元のclassを渡し、そのclassをVM内で継承することにより解決することができます。

const util = require('util');
const inherit = 'util.inherits(Array, classMap.Array);';
const code = `() => []`;
const func = vm.runInNewContext(`${inherit}${code}`, { util, classMap: { Array } });
const res = func();
console.log(res instanceof Array); // true
console.log(res instanceof Object); // true

他のclassも同様な問題があるため、こちらから適宜追加してください。

まとめ

NodeでLambdaのような機能を提供するサーバの構築方法を紹介しました。 750KBくらいのスクリプトを使用していますが、VMによるオーバーヘッドは5ms程度で無視できるレベルかと思います。

VMは様々な用途に使用でき、Nodeの幅が広がる面白いモジュールです。 VM使ったライブラリも公開してます、ぜひ試してみてください。

リンク

Node.js with TypeScriptのCode Coverageを計測する

概要

Node.js with TypeScriptでcode coverageを計測できるようにしたので、その導入手順について書きます。

NYCのセットアップ

はじめにNYCのセットアップします。 NYCとはIstanbulのコマンドラインツールで、Istanbulとはcode coverageを計測してくれるツールです。

まずはnycts-nodeをinstallします。

$ npm i -D nyc ts-node mocha

次にnycのtaskをpackage.jsonのscriptに追加します。今回はtestファイルをtest directory配下に置いています。

// package.json
{
  ...
  "scripts": {
    "test": "nyc mocha test/*"
  },
  ...
}

次にnycのconfigをpackage.jsonに設定します。必要に応じて設定を変更してください。

// package.json
{
  ...
  "nyc": {
    "include": [
      "lib/**/*.ts"
    ],
    "extension": [
      ".ts"
    ],
    "require": [
      "ts-node/register"
    ],
    "reporter": [
      "text",
      "text-summary",
      "html"
    ],
    "sourceMap": true
  },
  ...
}

opn-cliの設定(オプション)

coverage計測後、ぱっと見れるようにopn-cliの設定も一緒にpackage.jsonに追加します。

$ npm i -D opn-cli
// package.json
{
  ...
  "scripts": {
    "test": "nyc mocha test/*",
    "open:cov": "opn coverage/index.html"
  },
  ...
}

codecovの設定(オプション)

codecovで可視化したいのでcodecovの設定も追加します。codecovはCircleCIから自動的に走らせています。

$ npm i -D codecov
// package.json
{
  ...
  "scripts": {
    "test": "nyc mocha test/*",
    "codecov": "nyc report --reporter=lcovonly && codecov",
    "open:cov": "opn coverage/index.html"
  },
  ...
}

実行

後はnpm testを実行すると結果を見ることができます。nycのHTMLはbranchの詳細まで見れるのでとても便利です。

$ npm test
$ npm run open:cov

まとめ

CodeのCoverageが全てではないですが、テストを書く習慣・目標値の設定としてはとても良い指標だと思います。 これに加え、質の良いテストがかけるように心がけていきたいです。

ちなみにNYCはNew York Cityだそうです。

リンク

https://istanbul.js.org/ https://github.com/istanbuljs/nyc http://azimi.me/2016/09/30/nyc-mocha-typescript.1.html