athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

Visual StudioでC#とTypeScriptのUnitTestを一覧してみる。

ASP.NET WebForm系システムの駆逐まであと一歩。情報システム部の中嶋です。
弊社のシステム(特に社内向け)は、ASP.NET MVCASP.NET Coreで構築しているものが多数*1あります。そして、ASP.NET MVCの開発にはVisual Studioを使用*2しています。また、Web UIの開発を行っている以上、当然、.NET系の言語だけでは収まらずTypeScript*3も併用しながらの開発となります。
Visual Studio 2017は、C#などの.NET系の言語だけではなくTypeScriptでも定義や参照の検索が行え、インテリセンスも効くので非常に便利です。また、テストエクスプローラーではUnitTestの一覧が表示でき、一覧からテストを選択して実行することもできます。
が、しかし、テストエクスプローラーではTypeScriptのテストは表示されないため、TypeScriptのUnitTestだけはコマンドラインから実行することになります。
「なんか、ちょっともったいないな」と思っていたら、ありました。機能拡張が。マイクロソフトの井上章さんも紹介されている「Chutzpah Test Adapter for the Test Explorer(以下、Chutzpah)」です。
井上さんの記事は少し古くVisual Studio 2012でのお話なので、今回はVisual Studio 2017にChutzpahをインストールして使ってみたいと思います。

Chutzpahの動作概要

Chutzpahは、chutzpah.jsonで指定されたテスト対象コードおよびテストコードをテストハーネスとなるhtmlファイルから参照させ、それを同梱しているPhantomJS 2.1.1上で動作させています。 *4
テストハーネスとなるhtmlファイルは、設定ファイルで指定されたUnitTestエンジンをあらかじめ参照した形で自動的に生成されます。*5この時使用されるUnitTestエンジンは、Chutzpahに同梱されているQUnit 2.4.0、Jasmine 2.5.2、Mocha 3.2.0から選択することになります。*6

事前準備

まずテスト対象のシステムとして、ASP.NET MVCプロジェクトをUnitTest付きで用意しました。

TypeScriptのコードも用意しました。
ChutzpahSample/ChutzpahSample/TypeScript/base/core.ts*7

export class core {
    public static version = 8;
}

ChutzpahSample/ChutzpahSample/TypeScript/ui/ui.ts*8

import { core } from "base/core";

export class ui {
    public static displayVersion = "Version: " + core.version;
}

UnitTestエンジンにはJasmineを選択しました。またビルド時に依存関係を解決するものを用意しなかったので、PhantomJSにはRequireJSで解決してもらうことにしました。なお、これらはnpmで取得しています*9が、JasmineのjsファイルはChutzpahが同梱しているので型定義ファイルだけを取得しています。
ChutzpahSample/ChutzpahSample.Tests/package.json

{
  "name": "chutzpah-sample-test",
  "version": "1.0.0",
  "devDependencies": {
    "@types/jasmine": "~2.5.38",
    "requirejs": "^2.3.5"
  }
}

テストコードですが、Jasmineのjsファイルがビルド時に参照できる場所にないため、Jasmineだけreferenceタグで型定義ファイルを参照させています。
ChutzpahSample/ChutzpahSample.Tests/TypeScript/base/core.spec.ts*10

/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />

import { core } from “../../../ChutzpahSample/TypeScript/base/core”;

describe("base/core", () => {
    it("will return correct version from core", () => {
        let version = core.version;
        expect(version).toBe(8);
    });
});

ChutzpahSample/ChutzpahSample.Tests/TypeScript/ui/ui.spec.ts*11

/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />

import { ui } from "../../../ChutzpahSample/TypeScript/ui/ui";

describe("ui/ui", () => {
    it("will build display version", () => {
        let disp = ui.displayVersion;
        expect(disp).toBe("Version: 8");
    });
});

また、ChutzpahSample.Testsプロジェクト側のTypeScriptのコードがビルドされるように、ChutzpahSampleプロジェクト側の.csprojファイルから下記の2行をChutzpahSample.Testsプロジェクト側の.csprojファイルにコピーしました。

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

さらに、RequireJSを使うため両プロジェクトのTypeScriptの設定を変更し、モジュールシステムに「AMD」を指定しました。

ちなみに、この状態でビルドを行っても、テストエクスプローラーにはC#のテストしか表示されません。

Chutzpahのインストール

そこでChutzpahをインストールします。Chutzpahのインストールは機能拡張から「Chutzpah Test Adapter for the Test Explorer」を選択し、行います。
f:id:athomeNakajima:20180217124546p:plain

Chutzpahの設定

次にchutzpah.jsonファイルを作成します。設定内容は主に下記の6つです。

  1. UnitTestエンジンにはJasmineを使用しているので、「Framework」を「Jasmine」に。
  2. TypeScriptのコンパイルはChutzpahでは行わずVisual Studio任せにしたいので、「Compile」の「Mode」を「External」に。
  3. テストコードのパスはchutzpah.jsonファイルからの相対パスになるので、「Tests」の「Path」を「TypeScript」*12に。
  4. RequireJSを使用するため、「TestHarnessReferenceMode」を「AMD」に。
  5. 「References」に書くパスを設定ファイルからの相対パスにするため、「TestHarnessLocationMode」を「SettingsFileAdjacent」に。
  6. RequireJSを使用するため、「References」にRequireJSのパス「node_modules/requirejs/require.js」を設定。

ChutzpahSample/ChutzpahSample.Tests/chutzpah.json

{
  "Framework": "jasmine",
  "Compile": {
    "Mode": "External",
    "Extensions": [ ".ts" ],
    "ExtensionsWithNoOutput": [ ".d.ts" ]
  },
  "Tests": [
    {
      "Path": "TypeScript",
      "Includes": [ "*.ts" ]
    }
  ],
  "TestHarnessReferenceMode": "AMD",
  "TestHarnessLocationMode": "SettingsFileAdjacent",
  "References": [
    {
      "Path": "node_modules/requirejs/require.js",
      "IsTestFrameworkFile": true
    }
  ]
}

上記のファイルを保存し、Visual Studioを再起動した後、ビルドを行うとテストエクスプローラーにTypeScriptのUnitTestも表示されるようになります。が、ui.spec.tsにあるUnitTestがまだ表示されません。

これはui.spec.tsとui.tsが異なるプロジェクトにありパスの起点が異なるため、ui.spec.tsから間接的に参照されるcore.tsが探せないためです。そこで、require_config.jsを作成して、参照するパスを変更します。なお、このファイルに記載するパスは、ChutzpahSample.Testsプロジェクトのルートからの相対パスになります。
ChutzpahSample/ChutzpahSample.Tests/TypeScript/require_config.js

require.config({
    paths: {
        "base": "../ChutzpahSample/TypeScript/base",
        "ui": "../ChutzpahSample/TypeScript/ui"
    }
});

そして、このrequire_config.jsをPhantomJSが参照するようにchutzpah.jsonを変更します。*13
ChutzpahSample/ChutzpahSample.Tests/chutzpah.json

{
  "Framework": "jasmine",
  "Compile": {
(中略)
  "References": [
    {
      "Path": "node_modules/requirejs/require.js",
      "IsTestFrameworkFile": true
    },
    {
      "Path": "TypeScript/require_config.js"
    }
  ]
}

これで無事にui.spec.tsにあるUnitTestもテストエクスプローラーに表示されるようになります。

もしうまく行かない場合には、chutzpah.jsonにおいて「EnableTracing」を「true」にし、「TraceFilePath」を「chutzpah.log」と設定すると、chutzpah.jsonと同じフォルダにログが出力されるようになります。特にファイルが見つからないなどのエラーが発生した場合に、どのパスを探しているかを確認する時に重宝します。

まとめ

今回は新規のプロジェクトにChutzpahを導入してみましたが、既存のプロジェクトでも(Visual StudioにChutzpahをインストールしておけば)設定ファイルを書くだけでテストエクスプローラーからJavaScript/TypeScriptのテストが実行できるようになります。
CIサーバーではコマンドラインでの実行が必要なので別の手立てを用意する必要がありますが、コードを書いているときはGUIからC#とTypeScriptのUnitTestを一度に起動できるので、なかなか重宝しています。皆さんも活用してみてはいかがでしょうか。

*1:ブログ記事の数を見ると.NET系が多数派のように見えますが、実は社外向けを含めると少数派だったりします。増やしたい。

*2:ASP.NET Coreの場合は、Visual Studio Codeだったりもします。

*3:.NET系のチームはTypeScriptですが、他のチームはCoffeeScriptだったりES2015だったりします。

*4:このためTypeScriptの出力をES2015(またはそれ以降)にしていると、出力されたJavaScriptをPhantomJSが実行できないため、テストは動作しません。

*5:設定ファイルで指定することもできます。

*6:Jasmineは、1.x系も同梱されています。

*7:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/base/core.ts

*8:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/ui/screen.ts

*9:だいたいproxyでやられます。

*10:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/tests/base/base.qunit.test.ts

*11:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/tests/ui/ui.qunit.test.ts

*12:通常の設定では、.tsファイルと同じフォルダにも.jsファイルと.js.mapファイルが出力されているため、この設定で動作します。

*13:chutzpah.jsonを書き換えた後は、Visual Studioの再起動が必要です。