狠狠撸

狠狠撸Share a Scribd company logo
XunitとMoqの使い方
2017/3/11 1Copyright (c) 2017 Eiwa System Management, Inc.
株式会社永和システムマネジメント
コンサルティングセンター
センター長 天野勝
http://www.esm.co.jp/service/consulting/
? OS
? Windows10
? Visual Studio
? Visual Studio 2015 Community
2Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
動作確認環境
? 以下のサイトを参考にさせていただきました。
? ありがとうございます。
? xUnitを使用したC#のテストでSetUpやTearDown、Theory –
Symfoware
http://symfoware.blog68.fc2.com/blog-entry-1062.html
? 【ハウツー】Moqを活用して.NETでモックを使ったテストを行う | マイナ
ビニュース
http://news.mynavi.jp/articles/2009/06/15/moq/menu.html
? 【C#】【Mock】Moq(モッキュ) ~文法編~
http://blogs.yahoo.co.jp/dk521123/21216313.html
? Quickstart · moq/moq4 Wiki
https://github.com/Moq/moq4/wiki/Quickstart
? Moq.dllを使ってみた - 割と普通なブログ
http://normalian.hatenablog.com/entry/20090907/1252344523
? Moq(モックライブラリー)を使ってみる。 | 84zume Works
https://84zume.wordpress.com/2012/02/11/moqing/
3Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
謝辞
Xunit
2017/3/11 4Copyright (c) 2017 Eiwa System Management, Inc.
インストール
2017/3/11 5Copyright (c) 2017 Eiwa System Management, Inc.
? NuGetを起動する
? 「xunit」をインストールする
? 「xunit.runner.visualstudio」をインストールする
? Visual Studioから起動するためのテストランナー
6Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
NuGetからインストールする
テストケースクラスの作成
2017/3/11 7Copyright (c) 2017 Eiwa System Management, Inc.
? using Xunit
? publicなクラス
? publicな引数無しのメソッドに[Fact]を付ける
? 戻り値はあってもよい
? テストエクスプローラーから実行する
8Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
テストケース[Fact]
? テストケースの名称を変えたいとき
? [Fact(DisplayName = "名前")]
? テストケースをスキップしたいとき
? [Fact(Skip = "スキップする理由")]
9Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
テストケース[Fact]
? [InlineData()]で、テストメソッドに値を渡せる
? InlineData以外の方法もあり
? テストメソッドの引数と、同型、同数でなくてはならない
10Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
テストケース[Theory]
? using Xunit.Abstractions
? コンストラクターで出力先のストリーム(ITestOutputHelper)を
受け取り、WriteLine()を呼び出す
? テストエクスプローラーの「出力」をクリックして確認する
11Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
メッセージを出力をする場合
? SetUpはクラスのコンストラクタに書く
? TearDownはIDisposableを実装し、Disposeメソッドに書く
12Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
SetUpとTearDown
表明メソッド
2017/3/11 13Copyright (c) 2017 Eiwa System Management, Inc.
14Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
表明メソッド
[Fact(DisplayName = "標準的な表明メソッド")]
public void TestAssert()
{
// 2つの値が等しいか、否か
Assert.Equal(10, 0 + 10);
Assert.Equal("10", "" + 10);
Assert.NotEqual(0.3 + 0.6, 0.9);
// 2つのオブジェクトが同一か、否か
string s = "10";
Assert.Same(s, "10");
Assert.NotSame(s, "" + 10);
// nullかどうか
int? i = null;
Assert.Null(i);
i = 0;
Assert.NotNull(i);
// tureかfalseか
Assert.True((0xFF00 ^ 0x00FF) == 0xFFFF);
Assert.False((1 ^ 0) == -1);
}
15Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
表明メソッド
[Fact(DisplayName = "文字列の表明メソッド")]
public void TestStringAssert()
{
// 文字列を含むか、否か
Assert.Contains("BC", "ABCDEFG");
Assert.DoesNotContain("bc", "ABCDEFG");
// 文字列が空かどうか
Assert.Empty("");
Assert.NotEmpty("*");
// 指定した文字列で始まっているか
Assert.StartsWith("AB", "ABCDEF");
// 指定した文字列で終わっているか
Assert.EndsWith("YZ", "UVWXYZ");
// 正規表現を満たしているか、否か
Assert.Matches(@"No?.[0-9]{3}$", "No.789");
Assert.DoesNotMatch(@"No?.[0-9]{3}$", "No.7890");
}
16Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
表明メソッド
[Fact]
public void TestException()
{
// 例外が発生することを期待
Assert.ThrowsAny<ArgumentException>(() =>
{
throw new ArgumentNullException();
});
}
? 本系のサイトを参考
? https://github.com/xunit/samples.xunit
17Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
表明メソッドのサンプル
応用例
2017/3/11 18Copyright (c) 2017 Eiwa System Management, Inc.
19Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
CSVファイルをテストデータに使用する
a,b,result
1,2,3
4,5,9
10,11,21
TestData.csv
20Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
CSVファイルをテストデータに使用する
using Xunit;
using Xunit.Abstractions;
namespace FirstTest
{
public class CsvSample
{
private ITestOutputHelper _output;
public CsvSample(ITestOutputHelper output)
{
_output = output;
}
[Theory]
[CsvData("../../TestData.csv")]
public void TestUseCsvFile(int v1, int v2, int result)
{
_output.WriteLine(string.Format($"{v1},{v2},{result}"));
Assert.Equal(result, v1 + v2);
}
}
}
CsvSample.cs
21Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
CSVファイルをテストデータに使用する
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit.Sdk;
using System.Reflection;
using System.IO;
namespace FirstTest
{
// 独自の属性の定義
// 属性の対象はメソッド
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CsvDataAttribute : DataAttribute // クラス名から「Attribute」を除いた文字列が属性名になる
{
private string _filename;
// 1つの値をとるコンストラクタ
public CsvDataAttribute(string filename)
{
_filename = filename;
}
CsvDataAttribute.cs (1/3)
22Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
CSVファイルをテストデータに使用する
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
ParameterInfo[] pars = testMethod.GetParameters();
return DataSource(pars.Select((par) => par.ParameterType).ToArray());
}
private IEnumerable<object[]> DataSource(Type[] parameterTypes)
{
using (var reader = new StreamReader(_filename))
{
string line = reader.ReadLine(); // 1行目はヘッダーなので読み飛ばす
for (line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
var rowData = line.Split(','); // 単純にカンマで区切って配列にする
yield return ConvertParameters(rowData, parameterTypes);
}
}
}
CsvDataAttribute.cs (2/3)
23Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
CSVファイルをテストデータに使用する
private static object[] ConvertParameters(object[] values, Type[] parameterTypes)
{
object[] result = new object[values.Length];
for (int i = 0; i < values.Length; i++)
{
result[i] = ConvertParameter(values[i], i >= parameterTypes.Length ? null : parameterTypes[i]);
}
return result;
}
private static object ConvertParameter(object parameter, Type parameterType)
{
if ((parameter is double || parameter is float) &&
(parameterType == typeof(int) || parameterType == typeof(int?)))
{
int intValue;
string floatValueAsString = parameter.ToString();
if (Int32.TryParse(floatValueAsString, out intValue))
{
return intValue;
}
}
return parameter;
}
}
}
CsvDataAttribute.cs (3/3)
Moq
2017/3/11 24Copyright (c) 2017 Eiwa System Management, Inc.
インストール
2017/3/11 25Copyright (c) 2017 Eiwa System Management, Inc.
? NuGetを起動する
? 「Moq」をインストールする
26Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
NuGetからインストールする
使用方法
2017/3/11 27Copyright (c) 2017 Eiwa System Management, Inc.
28Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル テスト対象クラス
public interface ICalc
{
int Sub(int v1, int v2);
}
public class Calc : ICalc
{
// Stub化されるメソッドは、オーバーライド可能(virtual)にしておく
public virtual int Add(int v1, int v2)
{
return 100;
}
public int Sub(int v1, int v2)
{
throw new NotImplementedException();
}
}
Calc.cs
29Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル Setup、Returns
[Fact(DisplayName = "引数に値を指定してスタブ化")]
public void TestStubExactMatch()
{
var calcMock = new Mock<Calc>(); // Calcクラスをラップする
// CalcクラスのAddメソッドが、引数「1」「2」で呼ばれたら「3」を返す
calcMock.Setup(m => m.Add(1, 2)).Returns(3);
Calc c = calcMock.Object; // Calc形のインスタンスを取り出す
Assert.Equal(3, c.Add(1, 2)); // 指定した呼び出しなので、「3」を返す
Assert.Equal(0, c.Add(2, 1)); // 設定した引数の組み合わせ以外は「0」を返す
}
MoqStubSample.cs
30Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル It.IsAny
[Fact(DisplayName = "1つの引数に値を指定してスタブ化")]
public void TestStubAnyMatch()
{
var calcMock = new Mock<ICalc>(); // IClassインタフェースをラップ
// 第1引数はなんでもよい、第2引数が2ならばSubメソッドは3を返す
calcMock.Setup(m => m.Sub(It.IsAny<int>(), 2)).Returns(3);
ICalc c = calcMock.Object;
Assert.Equal(3, c.Sub(1, 2)); // 第2引数が2ならば3を返す
Assert.Equal(3, c.Sub(2, 2)); // 第2引数が2ならば3を返す
Assert.Equal(0, c.Sub(2, 1)); // 第2引数が2以外ならば0を返す
}
MoqStubSample.cs
31Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル When
[Fact(DisplayName = "Whenを使って、Setupの条件を指定する")]
public void TestWhen()
{
int count = 0;
var mock = new Mock<Calc>();
// countが偶数の時は、引数の値が何であろうと、Addメソッドは0を返す
mock.When(() => (count % 2 == 0)).Setup(
(m) => m.Add(It.IsAny<int>(), It.IsAny<int>()))
.Returns(0);
// countが奇数の時は、引数の値が何であろうと、Addメソッドは1を返す
mock.When(() => (count % 2 != 0)).Setup(
(m) => m.Add(It.IsAny<int>(), It.IsAny<int>()))
.Returns(1);
count = 10;
Assert.Equal(0, mock.Object.Add(0, 0)); // countが10(偶数)なので、0を返す
count = 11;
Assert.Equal(1, mock.Object.Add(0, 0)); // countが11(奇数)なので、1を返す
}
MoqStubSample.cs
32Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル WhenとInlineDataの組合せ
[Theory(DisplayName = "MoqのWhenと、XunitのInlineDataの組合せ")]
[InlineData(10, 0)]
[InlineData(11, 1)]
[InlineData(-10, 0)]
[InlineData(-11, 1)]
public void TestWhenAndInlineData(int count, int expected)
{
var mock = new Mock<Calc>();
// countが偶数の時は、引数の値が何であろうと、Addメソッドは0を返す
mock.When(() => (count % 2 == 0)).Setup(
(m) => m.Add(It.IsAny<int>(), It.IsAny<int>()))
.Returns(0);
// countが奇数の時は、引数の値が何であろうと、Addメソッドは1を返す
mock.When(() => (count % 2 != 0)).Setup(
(m) => m.Add(It.IsAny<int>(), It.IsAny<int>()))
.Returns(1);
Assert.Equal(expected, mock.Object.Add(0, 0));
}
MoqStubSample.cs
33Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル テスト対象クラス
public class JsonWriter : IDisposable
{
private TextWriter _writer;
public JsonWriter(TextWriter writer)
{
_writer = writer;
}
public void Dispose() => _writer.Close();
public void Write(string key, string value)
{
string s = string.Format(@"{{""{0}"":""{1}""}}", key, value);
_writer.Write(s); // 引数の型はstring
}
public void Write(string key, int value)
{
_writer.Write(@"{{""{0}"":{1}}}", key, value); // 引数の型はstring, string, int
}
public void Write(string key, double value)
{
_writer.Write(@"{{""{0}"":{1}}}", key, value); // 引数の型はstring, string, double
}
}
JsonWriter.cs
34Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル Callback
[Fact(DisplayName = "Callbackの使い方。整形してファイルに出力しているかを確認する")]
public void TestCallback()
{
var mock = new Mock<TextWriter>();
mock.Setup((m) => m.Write(It.IsAny<string>()))
.Callback<string>(s =>
{
Assert.Equal(@"{""name"":""テス太郎""}", s); // (1)
});
mock.Setup((m) => m.Write(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<int>()))
.Callback<string, object, object>((format, name, value) =>
{
Assert.Equal("age", name); // (2)
Assert.Equal(13, value);
});
mock.Setup((m) => m.Write(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<double>()))
.Callback<string, object, object>((format, name, value) =>
{
Assert.Equal("height", name); // (3)
Assert.Equal(175.5, value);
});
TextWriter tw = mock.Object;
var writer = new JsonWriter(tw);
writer.Write("name", "テス太郎"); // (1)が呼ばれる
writer.Write("age", 13); // (2)が呼ばれる
writer.Write("height", 175.5); // (3)が呼ばれる
MoqMockSample.cs
35Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル Throws
[Fact(DisplayName = "Throwsの使い方")]
public void TestThrows()
{
var mock = new Mock<TextWriter>();
mock.Setup(
(m) => m.Write(It.IsAny<string>())) // Writeメソッドが呼ばれると
.Throws<FileNotFoundException>(); // IOExceptionの派生クラスの
// FileNotFoundExeptionを発生させる
var writer = new JsonWriter(mock.Object);
Assert.ThrowsAny<IOException>(
() =>
{
writer.Write("name", "テス太郎");
});
}
MoqMockSample.cs
36Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル Verify
[Fact(DisplayName = "Verifyの使い方")]
public void TestVerify()
{
var mock = new Mock<TextWriter>();
var writer = new JsonWriter(mock.Object);
writer.Write("name", "テス太郎"); // 内部でstring, string, stringが呼ばれる。1回目
// Write(string, string, int)は1回も呼ばれていない
mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Never());
// Write(string, string, int)は最大でも1回しか呼ばれていない(実際は0回)
mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.AtMostOnce());
// Write(string, string, int)は最大でも3回しか呼ばれていない(実際は0回)
mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.AtMost(3));
// Write(string)は一回は呼ばれている
mock.Verify((m) => m.Write(It.IsAny<string>()), Times.AtLeastOnce());
// Write(string)は1回だけ呼ばれている
mock.Verify((m) => m.Write(It.IsAny<string>()), Times.Once());
writer.Write("name", "テス太郎"); // 内部でstring, string, stringが呼ばれる。2回目
// Write(string)は2回だけ呼ばれている
mock.Verify((m) => m.Write(It.IsAny<string>()), Times.Exactly(2));
}
MoqMockSample.cs
37Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11
サンプル VerifyAll
[Fact(DisplayName = "VerifyAllの使い方")]
public void TestVerifyAll()
{
var mock = new Mock<TextWriter>();
mock.Setup(
(m) => m.Write(It.IsAny<string>()));
var writer = new JsonWriter(mock.Object);
// SetUpした箇所がすべて呼ばれていないときにMockException発生
Assert.ThrowsAny<MockException>(
() =>
{
mock.VerifyAll();
});
}
MoqMockSample.cs

More Related Content

XunitとMoq 公開用

  • 1. XunitとMoqの使い方 2017/3/11 1Copyright (c) 2017 Eiwa System Management, Inc. 株式会社永和システムマネジメント コンサルティングセンター センター長 天野勝 http://www.esm.co.jp/service/consulting/
  • 2. ? OS ? Windows10 ? Visual Studio ? Visual Studio 2015 Community 2Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 動作確認環境
  • 3. ? 以下のサイトを参考にさせていただきました。 ? ありがとうございます。 ? xUnitを使用したC#のテストでSetUpやTearDown、Theory – Symfoware http://symfoware.blog68.fc2.com/blog-entry-1062.html ? 【ハウツー】Moqを活用して.NETでモックを使ったテストを行う | マイナ ビニュース http://news.mynavi.jp/articles/2009/06/15/moq/menu.html ? 【C#】【Mock】Moq(モッキュ) ~文法編~ http://blogs.yahoo.co.jp/dk521123/21216313.html ? Quickstart · moq/moq4 Wiki https://github.com/Moq/moq4/wiki/Quickstart ? Moq.dllを使ってみた - 割と普通なブログ http://normalian.hatenablog.com/entry/20090907/1252344523 ? Moq(モックライブラリー)を使ってみる。 | 84zume Works https://84zume.wordpress.com/2012/02/11/moqing/ 3Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 謝辞
  • 4. Xunit 2017/3/11 4Copyright (c) 2017 Eiwa System Management, Inc.
  • 5. インストール 2017/3/11 5Copyright (c) 2017 Eiwa System Management, Inc.
  • 6. ? NuGetを起動する ? 「xunit」をインストールする ? 「xunit.runner.visualstudio」をインストールする ? Visual Studioから起動するためのテストランナー 6Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 NuGetからインストールする
  • 8. ? using Xunit ? publicなクラス ? publicな引数無しのメソッドに[Fact]を付ける ? 戻り値はあってもよい ? テストエクスプローラーから実行する 8Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 テストケース[Fact]
  • 9. ? テストケースの名称を変えたいとき ? [Fact(DisplayName = "名前")] ? テストケースをスキップしたいとき ? [Fact(Skip = "スキップする理由")] 9Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 テストケース[Fact]
  • 10. ? [InlineData()]で、テストメソッドに値を渡せる ? InlineData以外の方法もあり ? テストメソッドの引数と、同型、同数でなくてはならない 10Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 テストケース[Theory]
  • 11. ? using Xunit.Abstractions ? コンストラクターで出力先のストリーム(ITestOutputHelper)を 受け取り、WriteLine()を呼び出す ? テストエクスプローラーの「出力」をクリックして確認する 11Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 メッセージを出力をする場合
  • 13. 表明メソッド 2017/3/11 13Copyright (c) 2017 Eiwa System Management, Inc.
  • 14. 14Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 表明メソッド [Fact(DisplayName = "標準的な表明メソッド")] public void TestAssert() { // 2つの値が等しいか、否か Assert.Equal(10, 0 + 10); Assert.Equal("10", "" + 10); Assert.NotEqual(0.3 + 0.6, 0.9); // 2つのオブジェクトが同一か、否か string s = "10"; Assert.Same(s, "10"); Assert.NotSame(s, "" + 10); // nullかどうか int? i = null; Assert.Null(i); i = 0; Assert.NotNull(i); // tureかfalseか Assert.True((0xFF00 ^ 0x00FF) == 0xFFFF); Assert.False((1 ^ 0) == -1); }
  • 15. 15Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 表明メソッド [Fact(DisplayName = "文字列の表明メソッド")] public void TestStringAssert() { // 文字列を含むか、否か Assert.Contains("BC", "ABCDEFG"); Assert.DoesNotContain("bc", "ABCDEFG"); // 文字列が空かどうか Assert.Empty(""); Assert.NotEmpty("*"); // 指定した文字列で始まっているか Assert.StartsWith("AB", "ABCDEF"); // 指定した文字列で終わっているか Assert.EndsWith("YZ", "UVWXYZ"); // 正規表現を満たしているか、否か Assert.Matches(@"No?.[0-9]{3}$", "No.789"); Assert.DoesNotMatch(@"No?.[0-9]{3}$", "No.7890"); }
  • 16. 16Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 表明メソッド [Fact] public void TestException() { // 例外が発生することを期待 Assert.ThrowsAny<ArgumentException>(() => { throw new ArgumentNullException(); }); }
  • 17. ? 本系のサイトを参考 ? https://github.com/xunit/samples.xunit 17Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 表明メソッドのサンプル
  • 18. 応用例 2017/3/11 18Copyright (c) 2017 Eiwa System Management, Inc.
  • 19. 19Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 CSVファイルをテストデータに使用する a,b,result 1,2,3 4,5,9 10,11,21 TestData.csv
  • 20. 20Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 CSVファイルをテストデータに使用する using Xunit; using Xunit.Abstractions; namespace FirstTest { public class CsvSample { private ITestOutputHelper _output; public CsvSample(ITestOutputHelper output) { _output = output; } [Theory] [CsvData("../../TestData.csv")] public void TestUseCsvFile(int v1, int v2, int result) { _output.WriteLine(string.Format($"{v1},{v2},{result}")); Assert.Equal(result, v1 + v2); } } } CsvSample.cs
  • 21. 21Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 CSVファイルをテストデータに使用する using System; using System.Collections.Generic; using System.Linq; using Xunit.Sdk; using System.Reflection; using System.IO; namespace FirstTest { // 独自の属性の定義 // 属性の対象はメソッド [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class CsvDataAttribute : DataAttribute // クラス名から「Attribute」を除いた文字列が属性名になる { private string _filename; // 1つの値をとるコンストラクタ public CsvDataAttribute(string filename) { _filename = filename; } CsvDataAttribute.cs (1/3)
  • 22. 22Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 CSVファイルをテストデータに使用する public override IEnumerable<object[]> GetData(MethodInfo testMethod) { ParameterInfo[] pars = testMethod.GetParameters(); return DataSource(pars.Select((par) => par.ParameterType).ToArray()); } private IEnumerable<object[]> DataSource(Type[] parameterTypes) { using (var reader = new StreamReader(_filename)) { string line = reader.ReadLine(); // 1行目はヘッダーなので読み飛ばす for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) { var rowData = line.Split(','); // 単純にカンマで区切って配列にする yield return ConvertParameters(rowData, parameterTypes); } } } CsvDataAttribute.cs (2/3)
  • 23. 23Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 CSVファイルをテストデータに使用する private static object[] ConvertParameters(object[] values, Type[] parameterTypes) { object[] result = new object[values.Length]; for (int i = 0; i < values.Length; i++) { result[i] = ConvertParameter(values[i], i >= parameterTypes.Length ? null : parameterTypes[i]); } return result; } private static object ConvertParameter(object parameter, Type parameterType) { if ((parameter is double || parameter is float) && (parameterType == typeof(int) || parameterType == typeof(int?))) { int intValue; string floatValueAsString = parameter.ToString(); if (Int32.TryParse(floatValueAsString, out intValue)) { return intValue; } } return parameter; } } } CsvDataAttribute.cs (3/3)
  • 24. Moq 2017/3/11 24Copyright (c) 2017 Eiwa System Management, Inc.
  • 25. インストール 2017/3/11 25Copyright (c) 2017 Eiwa System Management, Inc.
  • 26. ? NuGetを起動する ? 「Moq」をインストールする 26Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 NuGetからインストールする
  • 27. 使用方法 2017/3/11 27Copyright (c) 2017 Eiwa System Management, Inc.
  • 28. 28Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル テスト対象クラス public interface ICalc { int Sub(int v1, int v2); } public class Calc : ICalc { // Stub化されるメソッドは、オーバーライド可能(virtual)にしておく public virtual int Add(int v1, int v2) { return 100; } public int Sub(int v1, int v2) { throw new NotImplementedException(); } } Calc.cs
  • 29. 29Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル Setup、Returns [Fact(DisplayName = "引数に値を指定してスタブ化")] public void TestStubExactMatch() { var calcMock = new Mock<Calc>(); // Calcクラスをラップする // CalcクラスのAddメソッドが、引数「1」「2」で呼ばれたら「3」を返す calcMock.Setup(m => m.Add(1, 2)).Returns(3); Calc c = calcMock.Object; // Calc形のインスタンスを取り出す Assert.Equal(3, c.Add(1, 2)); // 指定した呼び出しなので、「3」を返す Assert.Equal(0, c.Add(2, 1)); // 設定した引数の組み合わせ以外は「0」を返す } MoqStubSample.cs
  • 30. 30Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル It.IsAny [Fact(DisplayName = "1つの引数に値を指定してスタブ化")] public void TestStubAnyMatch() { var calcMock = new Mock<ICalc>(); // IClassインタフェースをラップ // 第1引数はなんでもよい、第2引数が2ならばSubメソッドは3を返す calcMock.Setup(m => m.Sub(It.IsAny<int>(), 2)).Returns(3); ICalc c = calcMock.Object; Assert.Equal(3, c.Sub(1, 2)); // 第2引数が2ならば3を返す Assert.Equal(3, c.Sub(2, 2)); // 第2引数が2ならば3を返す Assert.Equal(0, c.Sub(2, 1)); // 第2引数が2以外ならば0を返す } MoqStubSample.cs
  • 31. 31Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル When [Fact(DisplayName = "Whenを使って、Setupの条件を指定する")] public void TestWhen() { int count = 0; var mock = new Mock<Calc>(); // countが偶数の時は、引数の値が何であろうと、Addメソッドは0を返す mock.When(() => (count % 2 == 0)).Setup( (m) => m.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns(0); // countが奇数の時は、引数の値が何であろうと、Addメソッドは1を返す mock.When(() => (count % 2 != 0)).Setup( (m) => m.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns(1); count = 10; Assert.Equal(0, mock.Object.Add(0, 0)); // countが10(偶数)なので、0を返す count = 11; Assert.Equal(1, mock.Object.Add(0, 0)); // countが11(奇数)なので、1を返す } MoqStubSample.cs
  • 32. 32Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル WhenとInlineDataの組合せ [Theory(DisplayName = "MoqのWhenと、XunitのInlineDataの組合せ")] [InlineData(10, 0)] [InlineData(11, 1)] [InlineData(-10, 0)] [InlineData(-11, 1)] public void TestWhenAndInlineData(int count, int expected) { var mock = new Mock<Calc>(); // countが偶数の時は、引数の値が何であろうと、Addメソッドは0を返す mock.When(() => (count % 2 == 0)).Setup( (m) => m.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns(0); // countが奇数の時は、引数の値が何であろうと、Addメソッドは1を返す mock.When(() => (count % 2 != 0)).Setup( (m) => m.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns(1); Assert.Equal(expected, mock.Object.Add(0, 0)); } MoqStubSample.cs
  • 33. 33Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル テスト対象クラス public class JsonWriter : IDisposable { private TextWriter _writer; public JsonWriter(TextWriter writer) { _writer = writer; } public void Dispose() => _writer.Close(); public void Write(string key, string value) { string s = string.Format(@"{{""{0}"":""{1}""}}", key, value); _writer.Write(s); // 引数の型はstring } public void Write(string key, int value) { _writer.Write(@"{{""{0}"":{1}}}", key, value); // 引数の型はstring, string, int } public void Write(string key, double value) { _writer.Write(@"{{""{0}"":{1}}}", key, value); // 引数の型はstring, string, double } } JsonWriter.cs
  • 34. 34Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル Callback [Fact(DisplayName = "Callbackの使い方。整形してファイルに出力しているかを確認する")] public void TestCallback() { var mock = new Mock<TextWriter>(); mock.Setup((m) => m.Write(It.IsAny<string>())) .Callback<string>(s => { Assert.Equal(@"{""name"":""テス太郎""}", s); // (1) }); mock.Setup((m) => m.Write(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<int>())) .Callback<string, object, object>((format, name, value) => { Assert.Equal("age", name); // (2) Assert.Equal(13, value); }); mock.Setup((m) => m.Write(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<double>())) .Callback<string, object, object>((format, name, value) => { Assert.Equal("height", name); // (3) Assert.Equal(175.5, value); }); TextWriter tw = mock.Object; var writer = new JsonWriter(tw); writer.Write("name", "テス太郎"); // (1)が呼ばれる writer.Write("age", 13); // (2)が呼ばれる writer.Write("height", 175.5); // (3)が呼ばれる MoqMockSample.cs
  • 35. 35Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル Throws [Fact(DisplayName = "Throwsの使い方")] public void TestThrows() { var mock = new Mock<TextWriter>(); mock.Setup( (m) => m.Write(It.IsAny<string>())) // Writeメソッドが呼ばれると .Throws<FileNotFoundException>(); // IOExceptionの派生クラスの // FileNotFoundExeptionを発生させる var writer = new JsonWriter(mock.Object); Assert.ThrowsAny<IOException>( () => { writer.Write("name", "テス太郎"); }); } MoqMockSample.cs
  • 36. 36Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル Verify [Fact(DisplayName = "Verifyの使い方")] public void TestVerify() { var mock = new Mock<TextWriter>(); var writer = new JsonWriter(mock.Object); writer.Write("name", "テス太郎"); // 内部でstring, string, stringが呼ばれる。1回目 // Write(string, string, int)は1回も呼ばれていない mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Never()); // Write(string, string, int)は最大でも1回しか呼ばれていない(実際は0回) mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.AtMostOnce()); // Write(string, string, int)は最大でも3回しか呼ばれていない(実際は0回) mock.Verify((m) => m.Write(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.AtMost(3)); // Write(string)は一回は呼ばれている mock.Verify((m) => m.Write(It.IsAny<string>()), Times.AtLeastOnce()); // Write(string)は1回だけ呼ばれている mock.Verify((m) => m.Write(It.IsAny<string>()), Times.Once()); writer.Write("name", "テス太郎"); // 内部でstring, string, stringが呼ばれる。2回目 // Write(string)は2回だけ呼ばれている mock.Verify((m) => m.Write(It.IsAny<string>()), Times.Exactly(2)); } MoqMockSample.cs
  • 37. 37Copyright (c) 2017 Eiwa System Management, Inc.2017/3/11 サンプル VerifyAll [Fact(DisplayName = "VerifyAllの使い方")] public void TestVerifyAll() { var mock = new Mock<TextWriter>(); mock.Setup( (m) => m.Write(It.IsAny<string>())); var writer = new JsonWriter(mock.Object); // SetUpした箇所がすべて呼ばれていないときにMockException発生 Assert.ThrowsAny<MockException>( () => { mock.VerifyAll(); }); } MoqMockSample.cs