1.21 jigowatts

Great Scott!

【.NET Core】MoqのQuickstartをやってみる(Matching Arguments)

概要

前回.NET Coreのチュートリアルをやってみた流れでMoqのQuickstartもやってみます。こちらも以前、雑に触ってみた続きになります。

github.com

環境
  • macOS Sierra バージョン 10.12.6
  • .NET Core 2.0
  • Moq 4.7.137

テストプロジェクトにパッケージ参照追加

$ dotnet add package moq

テストプロジェクトファイルの中身を見てみるとMoqが追記されました。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
    <PackageReference Include="moq" Version="4.7.137" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
    <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\PrimeService\PrimeService.csproj" />
  </ItemGroup>
</Project>

ベースになるインターフェイスとクラスの作成

■MoqQuickstart.cs

namespace MoqQuickstart
{
    public interface IFoo
    {
        Bar Bar { get; set; }
        string Name { get; set; }
        int Value { get; set; }
        bool DoSomething(string value);
        bool DoSomething(int number, string value);
        string DoSomethingStringy(string value);
        bool TryParse(string value, out string outputValue);
        bool Submit(ref Bar bar);
        int GetCount();
        bool Add(int value);
    }

    public class Bar 
    {
        public virtual Baz Baz { get; set; }
        public virtual bool Submit() { return false; }
    }

    public class Baz
    {
        public virtual string Name { get; set; }
    }
}

テストプロジェクトにテストクラスを作成

■MatchingArguments.cs

using System;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using MoqQuickstart;

namespace MoqQuickstart.Tests
{
    [TestClass]
    public class MatchingArguments
    {
        [TestMethod]
        public void any_value()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.DoSomething(It.IsAny<string>())).Returns(true);

            var f = mock.Object;
            var actual = f.DoSomething("hoge");

            Assert.IsTrue(actual);
        }

        [DataTestMethod]
        [DataRow(-1)]
        [DataRow(1)]
        [DataRow(3)]
        public void matching_Func_int_lazy_evaluated(int value)
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); 
            
            var f = mock.Object;
            var actual = f.Add(value);

            Assert.IsFalse(actual, $"{value} has a remainder of 0");
        }

        [DataTestMethod]
        [DataRow(-1)]
        [DataRow(11)]
        public void matching_ranges(int value)
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns(true); 

            var f = mock.Object;
            var actual = f.Add(value);

            Assert.IsFalse(actual, $"{value} is in range");
        }

        [DataTestMethod]
        [DataRow("a")]
        [DataRow("A")]
        [DataRow("hack")]
        public void matching_regex(string value)
        {
            var mock = new Mock<IFoo>();
            mock.Setup(x => x.DoSomethingStringy(It.IsRegex("[a-d]+", RegexOptions.IgnoreCase))).Returns("foo");

            var f = mock.Object;
            var actual = f.DoSomethingStringy(value);

            Assert.AreEqual("foo", actual);            
        }
    }
}

指定したフルネームでテストを実行

メソッド名まで指定しないとダメっぽい。メソッド単位でテストしたいときに。

$ dotnet test --filter "FullyQualifiedName=MoqQuickstart.Tests.MatchingArguments.any_value"
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...

テストの合計数: 1。成功: 1。失敗:0。スキップ: 0。
テストの実行に成功しました。
テスト実行時間: 2.6119 秒

指定した名前を含むテストを実行

クラス単位で指定してみます。名前空間単位でも指定できたのでお好みの範囲で。

$ dotnet test --filter "FullyQualifiedName~MoqQuickstart.Tests.MatchingArguments"
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...

テストの合計数: 9。成功: 9。失敗:0。スキップ: 0。
テストの実行に成功しました。
テスト実行時間: 3.3198 秒

今までIt.IsAnyメソッド一本槍でした(足軽感)。

【.NET Core】MSTestのチュートリアルをやってみる

概要

.NET Core 2.0がリリースされたのでMSTestのチュートリアルをやってみます。

docs.microsoft.com

環境

ソリューションファイルの作成

$ mkdir unit-testing-using-dotnet-test
$ cd unit-testing-using-dotnet-test/
$ $ dotnet new sln

ソリューションファイルの中身

$ cat unit-testing-using-dotnet-test.sln 

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Debug|x64 = Debug|x64
		Debug|x86 = Debug|x86
		Release|Any CPU = Release|Any CPU
		Release|x64 = Release|x64
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal

クラスライブラリプロジェクトの作成

$ mkdir PrimeService
$ cd PrimeService/
$ dotnet new classlib

クラスライブラリプロジェクトファイルの中身

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

PrimeServiceクラスの作成

■PrimeService/PrimeService.cs

$ mv Class1.cs PrimeService.cs
using System;

namespace Prime.Services
{
    public class PrimeService
    {
        public bool IsPrime(int candidate)
        {
            throw new NotImplementedException("Please create a test first");
        }
    }
}

クラスライブラリプロジェクトをソリューションに追加

$ cd ../
$ dotnet sln add PrimeService/PrimeService.csproj 

テストプロジェクトの作成

MSTestのテンプレートを使用します。

$ mkdir PrimeService.Tests
$ cd PrimeService.Tests/
$ dotnet new mstest

テストプロジェクトファイルの中身

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>

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

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
    <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
  </ItemGroup>

</Project>

クラスライブラリプロジェクトの参照追加

$ dotnet add reference ../PrimeService/PrimeService.csproj 

テストプロジェクトをソリューションに追加

$ cd ../
$ dotnet sln add PrimeService.Tests/PrimeService.Tests.csproj 

テストクラスの作成

■PrimeService.Tests/PrimeService_IsPrimeShould.cs

$ cd PrimeService.Tests/
$ mv UnitTest1.cs  PrimeService_IsPrimeShould.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Prime.Services;

namespace Prime.UnitTests.Services
{
    [TestClass]
    public class PrimeService_IsPrimeShould
    {
        private readonly PrimeService _primeService;

        public PrimeService_IsPrimeShould()
        {
            _primeService = new PrimeService();
        }

        [TestMethod]
        public void ReturnFalseGivenValueOf1()
        {
            var result = _primeService.IsPrime(1);

            Assert.IsFalse(result,"1 should not be prime");
        }
    }
}

クラスライブラリプロジェクトのビルド

コンソールにはビルド結果が以下のような感じで出力されました。

$ cd ../PrimeService
$ dotnet build
.NET Core 向け Microsoft (R) Build Engine バージョン 15.3.409.57025
Copyright (C) Microsoft Corporation.All rights reserved.

  PrimeService -> /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService/bin/Debug/netstandard2.0/PrimeService.dll

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:02.03

単体テストの実行

テストプロジェクト側に戻ってテストを実行します。

$ cd ../PrimeService.Tests/
$ dotnet test
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
失敗   Prime.UnitTests.Services.PrimeService_IsPrimeShould.ReturnFalseGivenValueOf1
エラー メッセージ:
 Test method Prime.UnitTests.Services.PrimeService_IsPrimeShould.ReturnFalseGivenValueOf1 threw exception: 
System.NotImplementedException: Please create a test first
スタック トレース:
    at Prime.Services.PrimeService.IsPrime(Int32 candidate) in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService/PrimeService.cs:line 9
   at Prime.UnitTests.Services.PrimeService_IsPrimeShould.ReturnFalseGivenValueOf1() in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/PrimeService_IsPrimeShould.cs:line 19


テストの合計数: 1。成功: 0。失敗:1。スキップ: 0。
テストの実行に失敗しました。
テスト実行時間: 3.0107 秒

失敗します。

PrimeServiceクラスを修正しましょう。

■PrimeService/PrimeService.cs

using System;

namespace Prime.Services
{
    public class PrimeService
    {
        public bool IsPrime(int candidate)
        {
            if (candidate == 1)
            {
                return false;
            }
            throw new NotImplementedException("Please create a test first");
        }
    }
}

再度クラスライブラリプロジェクトをビルドしてテストを実行するとテストが成功しました。

$ dotnet test
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...

テストの合計数: 1。成功: 1。失敗:0。スキップ: 0。
テストの実行に成功しました。
テスト実行時間: 2.2823 秒

次回はこれをベースに、前回のMoqのquickstartの続きをやってみようと思います。

【C#】MoqのQuickstartをやってみる

参考
https://github.com/Moq/moq4/wiki/Quickstart

Methods

まずはこの辺のメソッドを押さえておけばいいのかな。

  1. Returns
  2. Throws
  3. Callback
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace MoqQuickStart
{
    [TestClass]
    public class Methods
    {
        [TestMethod]
        public void Practice()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.DoSomething("ping")).Returns(true);

            var f = mock.Object;
            Assert.IsFalse(f.DoSomething("pong"));
            Assert.IsTrue(f.DoSomething("ping"));
        }

        [TestMethod]
        public void Out_argument()
        {
            var outString = "ack";
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);

            var f = mock.Object;
            Assert.IsFalse(f.TryParse("pong", out outString));
            Assert.IsTrue(f.TryParse("ping", out outString));
        }

        [TestMethod]
        public void Ref_argument()
        {
            var instance = new Bar();
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.Submit(ref instance)).Returns(true);

            var f = mock.Object;
            Assert.IsTrue(f.Submit(ref instance));
        }

        [TestMethod]
        public void Access_invocation_arguments_when_returning_a_value()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(x => x.DoSomethingStringy(It.IsAny<string>()))
                                        .Returns((string s) => s.ToLower());
            var f = mock.Object;
            var actual = f.DoSomethingStringy("HOGE");

            Assert.AreEqual("hoge", actual);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void Throwing_when_invoked_with_specific_parameters_1()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.DoSomething("reset")).Throws<InvalidOperationException>();
            mock.Object.DoSomething("reset");
            Assert.Fail();
        }

        [TestMethod]
        public void Throwing_when_invoked_with_specific_parameters_2()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.DoSomething("")).Throws(new ArgumentException("command"));

            try
            {
                mock.Object.DoSomething("");
                Assert.Fail();
            }
            catch (ArgumentException ex)
            {
                Assert.AreEqual("command",ex.Message);
            }
        }

        [TestMethod]
        public void Lazy_evaluating_return_value()
        {
            var count = 1;
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.GetCount()).Returns(() => count);

            var actual = mock.Object.GetCount();
            Assert.AreEqual(1, actual);
        }

        [TestMethod]
        public void Returning_different_values_on_each_invocation()
        {
            var mock = new Mock<IFoo>();
            var calls = 0;
            mock.Setup(foo => foo.GetCount())
                .Returns(() => calls)
                .Callback(() => calls++);

            Console.WriteLine(mock.Object.GetCount());
            Assert.AreEqual(1, calls);

            Console.WriteLine(mock.Object.GetCount());
            Assert.AreEqual(2, calls);
        }
    }
}