.NET CoreのテストランナーにNUnitを使う方法

2016年6月24日金曜日

.NET Coreでの自動テスト

.NET Coreは、基本コマンドの中にテストランナーの実行サブコマンドが含みます。
project.jsonファイル内に"testRunner": "..."という記述をおこなっておくと、プロジェクトディレクトリ内で
$ dotnet test
とするだけでビルドからテスト実施、レポート(XMLファイル)出力までを一気に実行できます。

.NETではふつうxUnit使うよね、という風潮

.NET Coreでのテスト実行については公式ヘルプページ(Unit Testing in .NET Core using dotnet test)があります。
このドキュメント内にはxUnitを利用する手順しか書かれていません。しかし個人的にはxUnitのキラキラしたオシャレ雰囲気があまり好きではなくて、泥臭さを感じるNUnitのほうが好みです。
.NET Coreのproject.json内での"testRunner": "foobar"という記述を解釈したランタイムは、src/dotnet/commands/dotnet-test/ConsoleTestRunner.csに従ってdotnet-test-foobarというコマンドを探しにいきます。dotnet-test-nunitというコマンドが存在すればいい感じにテストランナーとして呼び出せるはずです。

.NET Coreにおけるコマンド解決の方法

コマンドを探しにいきますが、一体どのように対象を解決して実行するのでしょうか。
プロジェクトのbinディレクトリもしくはパッケージ参照パスから、一致する名前をもつexeまたはdllを拾ってくることは想像がつきます。具体的にはsrc/Microsoft.DotNet.Cli.Utils/ProjectDependenciesCommandFactory.csを起点とするsrc/Microsoft.DotNet.Cli.Utils/CommandResolutionのコード群による解決がおこなわれます。
コマンド解決部分は1.0.0正式版リリースを前にしてもちょくちょく手が入っているので、正式版が出た後で機会があれば書いてみたいところです。

NUnitの.NET Core向けテストランナーalpha版の使い方

さて、xUnitではなくNUnitを使おうと試行錯誤すること数分、どうやらNUnitの.NET Core対応版がまだリリースされていないらしいことに気付きました。ゴリ押しする方法は提示されています。しかし.NET Core方式のほうが、テストランナーとのレポートのやり取り方法などをきっちり定義しているので、のちのちはCIでの状況モニタなどにおいて下回りのテストランナーを意識せずに結果を利用できるようになって嬉しいはずです。このため、せっかくなら.NET Coreの流儀でやりたいものです*1
実はnuget.orgでNUnitの.NET Core対応alpha版が配布されています*2
本エントリを書いているタイミングでの最新版はdotnet-test-nunit/3.4.0-alpha-1です。
使いかたはとてもシンプルです。マルチプロジェクト構成をとる場合は、さきほどのUnit Testing in .NET Core using dotnet testページのxUnit向けガイドに途中まで従ってglobal.jsonを書くなどしておきましょう。
そしてテストプロジェクト側では次のようなproject.jsonファイルを作成します。
{
  "version": "1.0.0-*",
  "dependencies": {
    "NETStandard.Library": "1.5.0-rc2-24027",
    "NUnit": "3.2.1",
    "NUnitLite": "3.2.1",
    "dotnet-test-nunit": "3.4.0-alpha-1",
    "ReVIEWBlogger": "1.0.0"
  },
  "testRunner": "nunit",
  "frameworks": {
    "netstandard1.5": {
      "imports": [
        "dnxcore50",
        "netcoreapp1.0",
        "portable-net45+win8"
      ]
    }
  },
  "runtimes": {
    "win10-x86": {},
    "win10-x64": {},
    "osx.10.11-x64": {},
    "debian.8-x64": {}
  }
}
ここで、"ReVIEWBlogger": "1.0.0"は私の手元のテスト対象パッケージです。手元の環境にあわせて適宜変更してください。
大事な記述が数点あるので整理します。
  • "testRunner": "nunit" .NET Coreへのテストランナー指定
  • "dependencies"
    • "NUnit": "3.2.1" NUnitの本体
    • "dotnet-test-nunit": "3.4.0-alpha-1" NUnitの.NET Core用テストランナー
  • "frameworks": { "netstandard1.5": "imports": [] }
    • "portable-net45+win8" dnxcore50未サポートの依存PCLパッケージ用*3
  • "runtimes" 実行環境に含めたいランタイムの一覧
    • テストランナーでxplatというのはまああんまり想定するものでないので、"platform"指定ではなく"platfroms":でそれぞれ指定していく感じです
    • 一般的なものはここでカバーしていますが、足りない場合は公式のRID一覧ページを参照してください
どれをサボってもうまく動作しないはずです。
ちなみにパス解決の都合上、project.jsonでの記述は"testRunner": "NUnit"などではなく"testRunner": "nunit"と表記する必要があります。私は当初NUnitと記載していて、以下のように「そんな実行ファイルないよ」と怒られていました。
dotnet-test Error: 0 : Microsoft.DotNet.Cli.Utils.CommandUnknownException: No executable found matching command "dotnet-test-NUnit"
   at Microsoft.DotNet.Cli.Utils.ProjectDependenciesCommandFactory.FindProjectDependencyCommands(String commandName, IEnumerable`1 commandArgs, String configuration, NuGetFramework framework, String outputPath, String buildBasePath, String projectDirectory)
   at Microsoft.DotNet.Cli.Utils.ProjectDependenciesCommandFactory.Create(String commandName, IEnumerable`1 args, NuGetFramework framework, String configuration)
   at Microsoft.DotNet.Tools.Test.ConsoleTestRunner.DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
   at Microsoft.DotNet.Tools.Test.TestCommand.DoRun(String[] args)
[*1] .NET Coreのテストランナーサポートについては、NUnitのGitHubリポジトリにてissue(https://github.com/nunit/nunit/issues/1371が立っていて、nuget.orgで配布を始める前の暫定版も配布されていましたが、このblogエントリを下書きで放置している間にクローズされました。
[*2] 本件を調べ始めた当初にはNUnit開発者のプライベートなNuGetリポジトリにてテスト版が公開されていたのですが、nuget.orgへ移動しました。
[*3] .NET Core環境を想定していないパッケージはまだまだ多いです。実際にはportable-net45+win8指定で問題なく動作するものも多いので、この記述は現在の.NET Core用project.json内で頻出します。

.NET Coreのコマンドラインからテストを実行してみる

次のようなコードを書いて実行してみます。
using NUnit.Framework;

namespace ConsoleApplication
{
    [TestFixture]
    public class Program1
    {
        [Test]
        public void TestExpectedToPass()
        {
            Assert.Pass();
        }

        [Test]
        public void TestExpectedToFail()
        {
            Assert.Fail("No luck!!");
        }
    }
}
このようになります。
$ dotnet test
Project ReVIEWBlogger (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Project ReVIEWBlogger.Tests (.NETStandard,Version=v1.5) will be compiled because inputs were modified
Compiling ReVIEWBlogger.Tests for .NETStandard,Version=v1.5

Compilation succeeded.
    0 Warning(s)
    0 Error(s)

Time elapsed 00:00:06.2825436


NUnit .NET Core Runner 3.4.0
Copyright (C) 2016 Charlie Poole
Runtime Environment
    OS Platform: Darwin
     OS Version: 10.11
        Runtime: osx.10.11-x64
Test Files
    /Users/muo/workspace/review-blogger/test/ReVIEWBlogger.Tests/bin/Debug/netstandard1.5/osx.10.11-x64/ReVIEWBlogger.Tests.dll
Errors and Failures
1) Failed : ConsoleApplication.TestsForTesting.TestExpectedToFail
No luck!!
at ConsoleApplication.TestsForTesting.TestExpectedToFail()
Run Settings
    WorkDirectory: /Users/muo/workspace/review-blogger/test/ReVIEWBlogger.Tests
Test Run Summary
  Overall result: Failed
  Test Count: 2, Passed: 1, Failed: 1, Inconclusive: 0, Skipped: 0
    Failed Tests - Failures: 1, Errors: 0, Invalid: 0
  Start time: 2016-06-13 06:39:19Z
    End time: 2016-06-13 06:39:19Z
    Duration: 0.637 seconds
Results saved as /Users/muo/workspace/review-blogger/test/ReVIEWBlogger.Tests/TestResult.xml
SUMMARY: Total: 1 targets, Passed: 0, Failed: 1.
正しく失敗しますね。
すばらしい。
すばらしいですね。

Project Tangoの最近の変更を軽く追いかけたメモ

2016年6月6日月曜日

C APIのヘッダ的な意味で。
じきにUE4+Tangoの組み合わせで使いたいので、ベースリビジョンにはhttps://github.com/opaquemultimedia/ProjectTangoPluginに取り込まれているものを利用。
そしてProject TangoのSDKダウンロードページhttps://developers.google.com/project-tango/downloads?hl=ja#project_tango_sdk_filesから得られる最新(2016年5月にリリースされた"Mira"のもの)を入手。
簡単にdiffを取れるかなーと思って開いてみると、doxygen用の記法を変えたようでほぼ全行のdiffが出てきた。最低限1行コメントを削ったり行末スペースを加工してdiffを取ったところ、それでも2,000行ぐらいのdiffがでてきた。
コメントの再フォーマットなどの影響をずいぶん受けていると感じたため、clocコマンドで複数行コメントを削り、それをある程度手加工したうえでclang-formatへ通して最低限まともな感じにした*1
得られたdiffがhttps://gist.github.com/muojp/9db610e6dc14be97a033a160c9d89674
いくつかenumの追加と構造体追加にそれら関連の定数追加、そして現状どこまで本気で動くか分からないユーティリティ関数がいくつか*2追加されている。
ん???
- TANGO_RECORDING_MODE_SCENE_RECONSTRUTCION = 1,
+ TANGO_RECORDING_MODE_SCENE_RECONSTRUCTION = 1,
どこが違うっけ、としばし悩んでRECONSTRUCTIONのtypo修正かーーーと気付いた。
Android本体でも時折あったやつですね。今回は時間切れにつきここまで。
UE4.11+に対応していない理由に関して、https://github.com/opaquemultimedia/ProjectTangoPlugin/issues/1#issuecomment-223446785になかなか悲しい事情が書かれてた。というわけでTango Coreチームの次リリース時にきっと更新される。
[*2] 元ファイルではexperimentalとわざわざ明記されているものも多い。