🐥note.

小鳥とMicrosoft <3 なエンジニアの技術Blog📚

.NET Core CLIのdotnet watchでチョットだけ快適なTDD開発

dotnet watchコマンドを使ってLive Unit TestっぽくTDDする話です。

はじめに

Visual Studio 2017以降Live Unit Testなる機能があります。
これはコードの変更中にバックグラウンドで自動的に実行し、結果をVisual Studio上に表示する、という機能です。 残念ながら使用するにはVisual Studio Enterprise エディションが必要です。

参考 Microsoft Docs - Visual Studio での Live Unit Testing

弊社はソフト側のエンジニアの地位が弱く、中々予算が下りないのでEnterpriseエディションなんて持ってる人いません。。
なので、ここはdotnet watchでなんちゃってLive Unit Testをします。

dotnet watchとは

ソースファイルや.csporjで指定したファイルが更新された際に自動的に.NET CLIコマンドを実行するツールです。

Visual Studio Codeでのやり方

Consoleアプリと、単体テストプロジェクトを作成します。

mkdir watchSample
cd watchSample
dotnet new sln
dotnet new console -o sampleApp
dotnet new xunit -o UnitTest
dotnet sln watchSample.sln add .\sampleApp\sampleApp.csproj
dotnet sln watchSample.sln add .\UnitTest\UnitTest.csproj
dotnet add .\UnitTest\UnitTest.csproj reference .\sampleApp\sampleApp.csproj
code .

単体テストを先に書きます。

UnitTest\UnitTest1.cs
using System;
using sampleApp;
using Xunit;

namespace UnitTest
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            Assert.Equal(2, Hoge.Sum(1, 1));
        }
    }
}

まだテスト対象の関数を作っていませんが、UnitTestプロジェクトに対してdotnet watchを使用します。

cd UnitTest
dotnet watch test

するとコンソールにこんな感じの表示がされるはずです。

f:id:piyo_esq:20190927204742p:plain
dotnet watch test 実行結果

ビルドが通っていないですね。 sampleAppにHogeクラスとSum関数を追加します。

sampleApp/Program.cs
using System;

namespace sampleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }

    public class Hoge
    {
        public static int Sum(int x, int y) => 0;
    }
}

追加した後ファイルを保存します。 ファイルを保存したタイミングで自動的に単体テストが実行されます。

f:id:piyo_esq:20190927204813p:plain
dotnet watch test 実行結果

テストに失敗してるらしいです。
いい感じですね。

関数の中身を書いて、テストを通しましょう。

using System;

namespace sampleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }

    public class Hoge
    {
        public static int Sum(int x, int y) => x + y;
    }
}

f:id:piyo_esq:20190927204824p:plain
dotnet watch test 実行結果

設定ファイルを監視対象にする

デフォルトだと以下のファイルが監視対象となっています。

  • */.cs
  • *.csproj
  • */.resx

例えばappSetting.jsonなどの設定ファイルが変更された際も自動的に単体テストが走るようにしたい場合、csprojを変更することで対応できます。

以下の例ではbin, objフォルダ以外の全jsonファイルを対象にしています。
ファイルはUnitTest側の.csprojを編集します。

UnitTest/UnitTest.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
    <PackageReference Include="coverlet.collector" Version="1.0.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\sampleApp\sampleApp.csproj" />
  </ItemGroup>

  <!-- 追加 -->
  <ItemGroup>
    <Watch Include="..\**\*.json" Exclude="..\**\bin\**\*.json;..\**\obj\**\*.json" />
  </ItemGroup>
  <!-- 追加 -->
</Project>

appSetting.jsonファイルをsampleApp側に追加して、jsonファイルを更新してみましょう。 自動的に単体テストが走るはずです。

特定のファイルを監視対象から外す

ウォッチするファイルのオプトアウトから引用するとこんな感じらしい。

<ItemGroup>
    <!-- exclude Generated.cs from dotnet-watch -->
    <Compile Include="Generated.cs" Watch="false" />

    <!-- exclude Strings.resx from dotnet-watch -->
    <EmbeddedResource Include="Strings.resx" Watch="false" />

    <!-- exclude changes in this referenced project -->
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" Watch="false" />
</ItemGroup>

おわり

Microsoft Docsを見ると.NET Coreと.NET Frameworkが混合したソリューションでも使えるっぽいですね。

また、watchコマンドはテストだけじゃなくdotnet watch run
ファイル更新時に自動的にビルド & 起動する、なんてこともできます。

Enterpriseエディションが買えない人もwatchコマンドで少しでもTDDが楽になるとよいですね。