「ASP.NET Web API/NUnitでユニットテストを行う」の版間の差分
(→概要) |
|||
(3人の利用者による、間の8版が非表示) | |||
1行目: | 1行目: | ||
− | ASP.NET Web | + | [[ASP.NET Web API]]の[[ユニットテスト]]を[[NUnit]]で行えると[[Xamarin Studio]]での[[開発]]が捗る。 |
==概要== | ==概要== | ||
[[Xamarin.iOS]]や[[Xamarin.Android]]は[[ユーザーインターフェース]]と[[ビジネスロジック]]の分離が売り文句である。 | [[Xamarin.iOS]]や[[Xamarin.Android]]は[[ユーザーインターフェース]]と[[ビジネスロジック]]の分離が売り文句である。 | ||
− | + | 動作環境に依存する[[ユーザーインターフェース]]と、非依存しない[[ビジネスロジック]]という考えである。 | |
− | + | しかしならが、実際のところ猫も杓子も[[クラウドコンピューティング]]を叫ぶご時世であり、また[[オンプレミス]]の[[社内システム]]も[[スタンドアローン]]で動くものは皆無である。[[HTML5]]はもとより、[[iPhone]]や[[Android]]のアプリのような[[リッチクライアント]]を用いる場合でも、核となる[[ビジネスロジック]]は[[サーバー]]上に[[Web API]]として実装される案件ばかりである。 | |
− | + | そうなると[[Xamarin]]ホゲホゲを使った[[リッチクライアント]]に対向する[[ビジネスロジック]]は[[ASP.NET Web API]]を用いて記述し、[[JSON]]や[[XML]]で[[データ]]のやりとりするのがもっとも手軽な実装方法であると思われ、このような状況下では[[クライアント]]も[[サーバー]]も[[Xamarin Studio]]で一元的に開発を行えると桁違いに捗り、[[Xamarin Studio]]に統合されている[[NUnit]]が活躍すること間違い無しである。 | |
==実装1== | ==実装1== | ||
− | + | ASP.NET Web APIのユニットテストはAPIコントローラーの各メソッドを[[NUnit]]から直接呼び出す方法が手っ取り早い。 | |
− | |||
− | + | しかしながら、[[Basic認証]]などのテストをしたい場合などもあるので、ここではASP.NET Web APIに含まれるインメモリサーバー(HttpServerクラス)を使用した方法を示す。テスト実行のために使用しているHttpClientクラスも地味に便利なので[[NUnit]]を使わない人も[[ググって]]みるといいと思う。 | |
− | |||
− | |||
− | + | ===手順1=== | |
− | + | 空のAPS.NETプロジェクトを作成する。 | |
− | |||
− | + | (1)ASP.NET Web APIを[[NuGet]]から入れる。 | |
+ | (2)動作検証用のAPIコントローラーを作る。 | ||
+ | : Controllers/HelloController.cs | ||
<source lang="csharp"> | <source lang="csharp"> | ||
+ | using System.Web.Http; | ||
+ | |||
+ | namespace SampleWebApi.Controllers | ||
+ | { | ||
+ | public class HelloController : ApiController | ||
+ | { | ||
+ | public string Get() | ||
+ | { | ||
+ | return "hello"; | ||
+ | } | ||
+ | |||
+ | public string Post([FromBody] string name) | ||
+ | { | ||
+ | return "hello, " + name; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | この例のPostメソッドでは単純な1個の[[引数]]のみを想定しているが、複数個になる場合はパラメータ用のクラスを作る、もしくはNewtonsoft.Json.LinqのJTokenクラスを使うのが望ましい。[[Xamarin]]系リッチクライアントとASP.NET Web APIの組み合わせの場合は、パラメータ用のクラス群を格納する共有用のライブラリプロジェクトを作り、それぞれのプロジェクトから参照して利用することで、[[インテリセンス]]などにより変数名のタイプミスなどの発見しづらい[[バグ]]を防ぐことができる。当然ながら[[JavaScript]]などから呼ぶ場合はタイプミスを防ぐ方法はないので手軽なJTokenを使う方法でも構わないと思う。 | ||
+ | |||
+ | ===手順2=== | ||
+ | まず「NUnitライブラリプロジェクト」を作る。 | ||
+ | [[Xamarin Studio]]の「新しいプロジェクト」ダイアログであれば「その他」「.NET」の中にある。 | ||
+ | |||
+ | 次に以下の手順を行う。 | ||
+ | * このプロジェクトに[[NuGet]]からASP.NET Web APIを追加する | ||
+ | * 手順1で作成したASP.NETプロジェクトを参照する | ||
+ | ** プロジェクトツリーの「参照」を右クリックし参照アセンブリの編集を選ぶ | ||
+ | ** Edit Referencesダイアログが開くので「Projects」タブを選ぶ | ||
+ | ** ソリューション内のプロジェクトの一覧が表示されるので手順1で作ったプロジェクトにチェックを入れる | ||
+ | |||
+ | ===手順3=== | ||
+ | インメモリサーバーの準備をする。 | ||
+ | |||
+ | なんでもいいのでNUnitライブラリプロジェクトからASP.NETプロジェクトのメソッドを呼び出す。 | ||
+ | これがとても重要。重要。 | ||
+ | なんでもいいがASP.NETプロジェクトにWebApiを初期化するコードを静的クラス・静的メソッドとして用意し、両方のプロジェクトで共有すると捗る。 | ||
+ | |||
+ | WebApiを初期化するコードの例 | ||
+ | :App_Startup/WebApiConfig.cs | ||
+ | <source lang="csharp"> | ||
+ | using System.Web.Http; | ||
+ | |||
+ | namespace SampleWebApi | ||
+ | { | ||
+ | public static class WebApiConfig | ||
+ | { | ||
+ | public static void Register(HttpConfiguration config) | ||
+ | { | ||
+ | // Attribute routing. | ||
+ | config.MapHttpAttributeRoutes(); | ||
+ | |||
+ | // Convention-based routing. | ||
+ | config.Routes.MapHttpRoute( | ||
+ | name: "DefaultApi", | ||
+ | routeTemplate: "api/{controller}/{id}", | ||
+ | defaults: new { id = RouteParameter.Optional } | ||
+ | ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ASP.NETプロジェクトはGlobal.asax.csから呼び出す。 | ||
+ | <source lang="csharp"> | ||
+ | using System.Web.Mvc; | ||
+ | using System.Web.Routing; | ||
+ | using System.Web.Http; | ||
+ | |||
+ | namespace SampleWebApi | ||
+ | { | ||
+ | public class MvcApplication : System.Web.HttpApplication | ||
+ | { | ||
+ | protected void Application_Start() | ||
+ | { | ||
+ | AreaRegistration.RegisterAllAreas(); | ||
+ | GlobalConfiguration.Configure(WebApiConfig.Register); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | NUnitプロジェクトはTestFixtureSetUp属性を付けたメソッドから呼び出す。 | ||
+ | <source lang="csharp"> | ||
+ | [TestFixtureSetUp] | ||
+ | public void Setup() | ||
+ | { | ||
+ | var config = new HttpConfiguration(); | ||
+ | SampleWebApi.WebApiConfig.Register(config); // ←←←これ重要 | ||
+ | config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ===手順4=== | ||
+ | NUnitライブラリプロジェクトにインメモリサーバー関連のコードを記述する。 | ||
+ | <source lang="csharp"> | ||
+ | using NUnit.Framework; | ||
+ | using System; | ||
+ | using System.Net; | ||
+ | using System.Net.Http; | ||
+ | using System.Net.Http.Headers; | ||
+ | using System.Net.Http.Formatting; | ||
+ | using System.Web.Http; | ||
+ | |||
+ | namespace SampleWebApi.Tests | ||
+ | { | ||
+ | [TestFixture] | ||
+ | public class InMemoryTests | ||
+ | { | ||
+ | private const string UrlBase = "http://127.0.0.1:8080/"; | ||
+ | |||
+ | private HttpServer _server; | ||
+ | |||
+ | [TestFixtureSetUp] | ||
+ | public void Setup() | ||
+ | { | ||
+ | var config = new HttpConfiguration(); | ||
+ | // ASP.NETプロジェクトの何かしらのメソッドを呼び出す。 | ||
+ | // 初期化コードを共通化しておくのが間違いないと思われる。 | ||
+ | SampleWebApi.WebApiConfig.Register(config); | ||
+ | // エラーをいっぱいだす | ||
+ | config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; | ||
+ | |||
+ | // 重要 | ||
+ | // ASP.NETプロジェクトの何かしらのメソッドを呼び出した後にインメモリサーバーのインスタンスを作ること | ||
+ | _server = new HttpServer(config); | ||
+ | } | ||
+ | |||
+ | public void Dispose() | ||
+ | { | ||
+ | if (_server != null) | ||
+ | { | ||
+ | _server.Dispose(); | ||
+ | _server = null; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | [Test] | ||
+ | public void Get() | ||
+ | { | ||
+ | using (var client = new HttpClient(_server)) | ||
+ | { | ||
+ | var request = CreateRequest( | ||
+ | "api/Hello", | ||
+ | "application/json", | ||
+ | HttpMethod.Get); | ||
+ | |||
+ | using (var response = client.SendAsync(request).Result) | ||
+ | { | ||
+ | Assert.IsNotNull(response); | ||
+ | Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); | ||
+ | Assert.NotNull(response.Content); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | [Test] | ||
+ | public async void Post() | ||
+ | { | ||
+ | using (var client = new HttpClient(_server)) | ||
+ | { | ||
+ | var request = CreateRequest( | ||
+ | "api/Hello", | ||
+ | "application/json", | ||
+ | HttpMethod.Post, | ||
+ | "monobook" , | ||
+ | new JsonMediaTypeFormatter()); | ||
+ | |||
+ | using (var response = client.SendAsync(request).Result) | ||
+ | { | ||
+ | Assert.IsNotNull(response); | ||
+ | Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); | ||
+ | Assert.NotNull(response.Content); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | private HttpRequestMessage CreateRequest(string url, string mthv, HttpMethod method) | ||
+ | { | ||
+ | var request = new HttpRequestMessage(); | ||
+ | request.RequestUri = new Uri(UrlBase + url); | ||
+ | request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mthv)); | ||
+ | request.Method = method; | ||
+ | return request; | ||
+ | } | ||
+ | |||
+ | private HttpRequestMessage CreateRequest<T>(string url, string mthv, HttpMethod method, T content, MediaTypeFormatter formatter) where T : class | ||
+ | { | ||
+ | HttpRequestMessage request = CreateRequest(url, mthv, method); | ||
+ | request.Content = new ObjectContent<T>(content, formatter); | ||
+ | return request; | ||
+ | } | ||
+ | } | ||
+ | } | ||
</source> | </source> | ||
+ | |||
+ | ===おわり=== | ||
==関連項目== | ==関連項目== | ||
33行目: | 227行目: | ||
{{stub}} | {{stub}} | ||
− | [[category:.NET Framework]] | + | [[category: .NET Framework]] |
+ | [[category: Xamarin.Studio]] |
2016年5月30日 (月) 01:03時点における最新版
ASP.NET Web APIのユニットテストをNUnitで行えるとXamarin Studioでの開発が捗る。
概要[編集 | ソースを編集]
Xamarin.iOSやXamarin.Androidはユーザーインターフェースとビジネスロジックの分離が売り文句である。 動作環境に依存するユーザーインターフェースと、非依存しないビジネスロジックという考えである。
しかしならが、実際のところ猫も杓子もクラウドコンピューティングを叫ぶご時世であり、またオンプレミスの社内システムもスタンドアローンで動くものは皆無である。HTML5はもとより、iPhoneやAndroidのアプリのようなリッチクライアントを用いる場合でも、核となるビジネスロジックはサーバー上にWeb APIとして実装される案件ばかりである。
そうなるとXamarinホゲホゲを使ったリッチクライアントに対向するビジネスロジックはASP.NET Web APIを用いて記述し、JSONやXMLでデータのやりとりするのがもっとも手軽な実装方法であると思われ、このような状況下ではクライアントもサーバーもXamarin Studioで一元的に開発を行えると桁違いに捗り、Xamarin Studioに統合されているNUnitが活躍すること間違い無しである。
実装1[編集 | ソースを編集]
ASP.NET Web APIのユニットテストはAPIコントローラーの各メソッドをNUnitから直接呼び出す方法が手っ取り早い。
しかしながら、Basic認証などのテストをしたい場合などもあるので、ここではASP.NET Web APIに含まれるインメモリサーバー(HttpServerクラス)を使用した方法を示す。テスト実行のために使用しているHttpClientクラスも地味に便利なのでNUnitを使わない人もググってみるといいと思う。
手順1[編集 | ソースを編集]
空のAPS.NETプロジェクトを作成する。
(1)ASP.NET Web APIをNuGetから入れる。
(2)動作検証用のAPIコントローラーを作る。
- Controllers/HelloController.cs
using System.Web.Http;
namespace SampleWebApi.Controllers
{
public class HelloController : ApiController
{
public string Get()
{
return "hello";
}
public string Post([FromBody] string name)
{
return "hello, " + name;
}
}
}
この例のPostメソッドでは単純な1個の引数のみを想定しているが、複数個になる場合はパラメータ用のクラスを作る、もしくはNewtonsoft.Json.LinqのJTokenクラスを使うのが望ましい。Xamarin系リッチクライアントとASP.NET Web APIの組み合わせの場合は、パラメータ用のクラス群を格納する共有用のライブラリプロジェクトを作り、それぞれのプロジェクトから参照して利用することで、インテリセンスなどにより変数名のタイプミスなどの発見しづらいバグを防ぐことができる。当然ながらJavaScriptなどから呼ぶ場合はタイプミスを防ぐ方法はないので手軽なJTokenを使う方法でも構わないと思う。
手順2[編集 | ソースを編集]
まず「NUnitライブラリプロジェクト」を作る。 Xamarin Studioの「新しいプロジェクト」ダイアログであれば「その他」「.NET」の中にある。
次に以下の手順を行う。
- このプロジェクトにNuGetからASP.NET Web APIを追加する
- 手順1で作成したASP.NETプロジェクトを参照する
- プロジェクトツリーの「参照」を右クリックし参照アセンブリの編集を選ぶ
- Edit Referencesダイアログが開くので「Projects」タブを選ぶ
- ソリューション内のプロジェクトの一覧が表示されるので手順1で作ったプロジェクトにチェックを入れる
手順3[編集 | ソースを編集]
インメモリサーバーの準備をする。
なんでもいいのでNUnitライブラリプロジェクトからASP.NETプロジェクトのメソッドを呼び出す。 これがとても重要。重要。 なんでもいいがASP.NETプロジェクトにWebApiを初期化するコードを静的クラス・静的メソッドとして用意し、両方のプロジェクトで共有すると捗る。
WebApiを初期化するコードの例
- App_Startup/WebApiConfig.cs
using System.Web.Http;
namespace SampleWebApi
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
ASP.NETプロジェクトはGlobal.asax.csから呼び出す。
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;
namespace SampleWebApi
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
NUnitプロジェクトはTestFixtureSetUp属性を付けたメソッドから呼び出す。
[TestFixtureSetUp]
public void Setup()
{
var config = new HttpConfiguration();
SampleWebApi.WebApiConfig.Register(config); // ←←←これ重要
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
}
手順4[編集 | ソースを編集]
NUnitライブラリプロジェクトにインメモリサーバー関連のコードを記述する。
using NUnit.Framework;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Formatting;
using System.Web.Http;
namespace SampleWebApi.Tests
{
[TestFixture]
public class InMemoryTests
{
private const string UrlBase = "http://127.0.0.1:8080/";
private HttpServer _server;
[TestFixtureSetUp]
public void Setup()
{
var config = new HttpConfiguration();
// ASP.NETプロジェクトの何かしらのメソッドを呼び出す。
// 初期化コードを共通化しておくのが間違いないと思われる。
SampleWebApi.WebApiConfig.Register(config);
// エラーをいっぱいだす
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// 重要
// ASP.NETプロジェクトの何かしらのメソッドを呼び出した後にインメモリサーバーのインスタンスを作ること
_server = new HttpServer(config);
}
public void Dispose()
{
if (_server != null)
{
_server.Dispose();
_server = null;
}
}
[Test]
public void Get()
{
using (var client = new HttpClient(_server))
{
var request = CreateRequest(
"api/Hello",
"application/json",
HttpMethod.Get);
using (var response = client.SendAsync(request).Result)
{
Assert.IsNotNull(response);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
}
}
}
[Test]
public async void Post()
{
using (var client = new HttpClient(_server))
{
var request = CreateRequest(
"api/Hello",
"application/json",
HttpMethod.Post,
"monobook" ,
new JsonMediaTypeFormatter());
using (var response = client.SendAsync(request).Result)
{
Assert.IsNotNull(response);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
}
}
}
private HttpRequestMessage CreateRequest(string url, string mthv, HttpMethod method)
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri(UrlBase + url);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mthv));
request.Method = method;
return request;
}
private HttpRequestMessage CreateRequest<T>(string url, string mthv, HttpMethod method, T content, MediaTypeFormatter formatter) where T : class
{
HttpRequestMessage request = CreateRequest(url, mthv, method);
request.Content = new ObjectContent<T>(content, formatter);
return request;
}
}
}