【.NET Core】MoqのQuickstartをやってみる(Properties)
概要
MoqのQuickstartをやってみる。次はプロパティの扱いについてです。
テストコード
書かれていない部分はこんな感じなのかなと考えながら書くのでミニヨクツクはず。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using MoqQuickstart; namespace MoqQuickstart.Tests { [TestClass] public class Properties { [TestMethod] public void setup() { var mock = new Mock<IFoo>(); mock.Setup(foo => foo.Name).Returns("bar"); Assert.AreEqual("bar",mock.Object.Name); } [TestMethod] public void auto_mocking_hierarchies() { var mock = new Mock<IFoo>(); mock.Setup(foo => foo.Bar.Baz.Name).Returns("baz"); Assert.AreEqual("baz",mock.Object.Bar.Baz.Name); } [TestMethod] public void expects_an_invocation_to_set_the_value_to_foo() { var mock = new Mock<IFoo>(MockBehavior.Strict); mock.SetupSet(foo => foo.Name = "foo"); mock.Object.Name = "foo"; } [TestMethod] public void or_verify_the_setter_directly() { var mock = new Mock<IFoo>(); mock.Object.Name = "foo"; mock.VerifySet(foo => foo.Name = "foo"); } [TestMethod] public void setup_a_property_so_that_it_will_automatically_start_tracking_its_value() { var mock = new Mock<IFoo>(); // start "tracking" sets/gets to this property mock.SetupProperty(f => f.Name); // alternatively, provide a default value for the stubbed property mock.SetupProperty(f => f.Name, "foo"); // Now you can do: IFoo foo = mock.Object; // Initial value was stored Assert.AreEqual("foo", foo.Name); // New value set which changes the initial value foo.Name = "bar"; Assert.AreEqual("bar", foo.Name); } [TestMethod] public void stub_all_properties_on_a_mock() { var mock = new Mock<IFoo>(); mock.SetupAllProperties(); mock.Object.Name = "foo"; mock.Object.Value = 123; Assert.AreEqual("foo", mock.Object.Name); Assert.AreEqual(123, mock.Object.Value); } } }
モックに対してプロパティを設定していく方法も何通りかあるんですね。
個人的にSetupSet
メソッドがよくわかりませんでした。 プロパティに"foo"が設定されることを期待しているということは、"foo"が設定されなかったらテスト失敗するのかと思っていたのですが、これだと失敗しません。
var mock = new Mock<IFoo>(); mock.SetupSet(foo => foo.Name = "foo"); mock.Object.Name = "bar";
リポジトリ内を検索してみたらMockBehavior.Strict
を渡しているサンプルを見つけました。厳密な振る舞いのモック?
var mock = new Mock<IFoo>(MockBehavior.Strict); mock.SetupSet(foo => foo.Name = "foo"); mock.Object.Name = "bar";
テストを実行してみると失敗します。
$ dotnet test --filter "FullyQualifiedName=MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo" 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. テスト実行を開始しています。お待ちください... 失敗 MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo エラー メッセージ: Test method MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo threw exception: Moq.MockException: IFoo.Name = "bar" invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup. スタック トレース: at Moq.ExtractProxyCall.HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx) at Moq.Interceptor.Intercept(ICallContext invocation) at Castle.DynamicProxy.AbstractInvocation.Proceed() at MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo() in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/Properties.cs:line 35 テストの合計数: 1。成功: 0。失敗:1。スキップ: 0。 テストの実行に失敗しました。 テスト実行時間: 2.4992 秒
他にはCallback
と組み合わせているコードがありました。
MockBehavior.Strict
にしてしまうとCallback
使うとき不便ですよね。
[TestMethod] public void setupSet_and_callback() { var mock = new Mock<IFoo>(); var when = true; var positive = false; mock.When(() => when).SetupSet(x => x.Name = "foo").Callback(() => positive = true); // Strictモードだと"baz"を設定した時点でテスト失敗 mock.Object.Name = "baz"; Assert.IsFalse(positive); mock.Object.Name = "foo"; Assert.IsTrue(positive); }
であればVerifySet
を使った方が、プロパティに"foo"が設定されることを期待しているケースでは直感的な感じ。
var mock = new Mock<IFoo>(); mock.Object.Name = "foo"; mock.VerifySet(foo => foo.Name = "foo");
手段が多いことはいいことですmo(^q^)
【.NET Core】MoqのQuickstartをやってみる(Matching Arguments)
テストプロジェクトにパッケージ参照追加
$ 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のチュートリアルをやってみます。
ソリューションファイルの作成
$ 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の続きをやってみようと思います。