作るもの
-
FizzBuzz
を返すWeb API
こんなリクエストを送ると...
http://localhost:55800/api/fizzbuzz?start=10&end=30
こんなのが返ってくるWebAPI
["Buzz","11","Fizz","13","14","FizzBuzz","16","17","Fizz","19","Buzz","Fizz","22","23","Fizz","Buzz","26","Fizz","28","29","FizzBuzz"]
C# とASP.NET Core でつくります。
- DI(依存性の注入)でサービスクラスを注入する
- xUnit を使用した自動テスト
- Swagger を使ったAPI仕様書
環境構築
ASP.NET Core Web APIのプロジェクトをつくります。
Visual C#
> Web
> .NET Core
> ASP.NET Core Web アプリケーション
> API
ソリューションにXUnitのプロジェクトを追加します。
Visual C#
> Web
> .NET Core
> xUnitテスト プロジェクト
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
</Project>
FizzBuzzサービスの作成とテスト
FizzBuzzService
を作成します。
あとからWeb API
のコントローラーにDIします。
FizzBuzzの仕様
- 1未満の値が渡されると例外をスローする。
- 3で割り切れる数字を渡すと、文字列
"Fizz"
を返す。 - 5で割り切れる数値を渡すと、文字列
"Buzz"
を返す。 - 15で割り切れる数値を渡すと、文字列
"FizzBuzz"
を返す。 - 上記以外の数値が渡されると、数値を文字列に変えて返す。
まず、FizzBuzzApiにインターフェースを作成します。
拡張が強くになる、Moq
でモックアップが簡単といったメリットがあります。
namespace FizzBuzzApi.Interfaces
{
public interface IFizzBuzz
{
string GetWord(int n);
}
}
インターフェースを実装したFizzBuzzService
クラスを作成します。
public class FizzBuzzService : IFizzBuzz { }
まで書きCtrl
+.
を押すと以下のようなスケルトンが自動実装されます。
using FizzBuzzApi.Interfaces;
using System;
namespace FizzBuzzApi.Services
{
public class FizzBuzzService : IFizzBuzz
{
public string GetWord(int n) => throw new NotImplementedException();
}
}
テスト駆動開発では テストを書く
> テストをパスする最小限のコードを書く
> リファクタリングする
を繰り返します。
FizzBuzzApiTest
の参照にFizzBuzzApi
を追加します。
Services
フォルダを作成し、FizzBuzzServiceTest.cs
を作成します。
過程を省略しますが、以下のようになりました。
using FizzBuzzApi.Services;
using System;
using Xunit;
namespace FizzBuzzApiTest.Services
{
public class FizzBuzzServiceTest
{
[Theory]
[InlineData(1, "1")]
[InlineData(2, "2")]
[InlineData(3, "Fizz")]
[InlineData(4, "4")]
[InlineData(5, "Buzz")]
[InlineData(6, "Fizz")]
[InlineData(7, "7")]
[InlineData(10, "Buzz")]
[InlineData(15, "FizzBuzz")]
[InlineData(30, "FizzBuzz")]
public void ReturnsValidString(int n, string word)
{
// sut はSystem under testを指す
var sut = new FizzBuzzService();
Assert.Equal(word, sut.GetWord(n));
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
public void LessThan1_ThrowsException(int n)
{
var sut = new FizzBuzzService();
var ex = Assert.Throws<ArgumentException>(() =>
{
sut.GetWord(n);
});
Assert.Contains("は1以上としてください", ex.Message);
}
}
}
using FizzBuzzApi.Interfaces;
using System;
namespace FizzBuzzApi.Services
{
public class FizzBuzzService : IFizzBuzz
{
public string GetWord(int n)
{
if (n < 1)
throw new ArgumentException($"{nameof(n)}は1以上としてください");
if (n % 15 == 0)
return "FizzBuzz";
else if (n % 3 == 0)
return "Fizz";
else if (n % 5 == 0)
return "Buzz";
else
return n.ToString();
}
}
}
テストはすべてパスしています。
FizzBuzzService
をDI(依存性の注入)する
ASP.NET Core には DI コンテナーがビルトインされています。
Startup.cs
の ConfigureServices
メソッドを編集します。
FizzBuzzService
に依存するクラスのコンストラクターの引数にFizzBuzzService
のインスタンスが渡されます。
...
public void ConfigureServices(IServiceCollection services) =>
services.AddTransient<IFizzBuzz, FizzBuzzService>()
.AddMvc();
...
HTTP要求を受け付けるコントローラーを実装します。
using System.Collections.Generic;
using System.Linq;
using FizzBuzzApi.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace FizzBuzzApi.Controllers
{
[Produces("application/json")]
[Route("api/FizzBuzz")]
public class FizzBuzzController : Controller
{
private readonly IFizzBuzz _fizzbuzz;
// Startup.cs の設定によりコンストラクタの引数に FizzBuzzService が渡される
public FizzBuzzController(IFizzBuzz fizzbuzz) =>
_fizzbuzz = fizzbuzz;
[HttpGet]
public IEnumerable<string> Get(int from, int to)
{
var count = to - from + 1;
return Enumerable.Range(from, count)
.Select(n => _fizzbuzz.GetWord(n));
}
}
}
アプリを起動したらPostmanでリクエストを送って動確します。
[
"Fizz",
"13",
"14",
"FizzBuzz",
"16",
"17",
"Fizz",
"19",
"Buzz",
"Fizz",
"22",
"23",
"Fizz",
"Buzz",
"26",
"Fizz",
"28",
"29",
"FizzBuzz",
"31",
"32",
"Fizz",
"34",
"Buzz"
]
powershell でも。
PS C:\Program Files\PowerShell\6.0.2> Invoke-WebRequest -Uri "http://localhost:49261/api/FizzBuzz?from=12&to=35"
StatusCode : 200
StatusDescription : OK
Content : ["Fizz","13","14","FizzBuzz","16","17","Fizz","19","Buzz","Fizz","22","23","Fizz","Buzz","26","Fizz
","28","29","FizzBuzz","31","32","Fizz","34","Buzz"]
RawContent : HTTP/1.1 200 OK
Date: Sat, 09 Jun 2018 03:35:52 GMT
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc2E1MDBcc291cmNlXHJlcG9zXEZpenpCdXp6QXBpXEZpenpCdXp6QXBpXGFwaV
xG...
Headers : {[Date, System.String[]], [Transfer-Encoding, System.String[]], [Server, System.String[]], [X-Sourc
eFiles, System.String[]]...}
Images : {}
InputFields : {}
Links : {}
RawContentLength : 151
RelationLink : {}
(powershell がいつの間にか Linux や mac 対応のクロスプラットフォームになっていました。PowerShell Core というそうです。変数などはすべて .NET
のオブジェクトであり、またコードネームがモナドというように関数型プログラミングが可能とモダンです。さすが名前に "power" がついていたり次世代 shell とか呼ばれるだけある。。。)
Swaggerドキュメントの出力
NSwag
と Swashbuckle
を使った方法がありますが、NSwag
を選択します。
Visual Studio のパッケージマネージャーコンソールで以下のコマンドを入力します。
PS > Install-Package NSwag.AspNetCore
Startup.cs
の Configure
メソッドに UseSwaggerUi
を追加します。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, settings =>
{
settings.GeneratorSettings.DefaultPropertyNameHandling =
PropertyNameHandling.CamelCase;
});
app.UseMvc();
}
アプリを起動し http://localhost:<port>/swagger
に移動すると、Swagger UI が表示されます。
http://localhost:<port>/swagger/v1/swagger.json
に移動すると、Swagger 仕様が表示されます。
{
"x-generator": "NSwag v11.17.13.0 (NJsonSchema v9.10.50.0 (Newtonsoft.Json v10.0.0.0))",
"swagger": "2.0",
"info": {
"title": "My Title",
"version": "1.0.0"
},
"host": "localhost:49261",
"schemes": [
"http"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/api/FizzBuzz": {
"get": {
"tags": [
"FizzBuzz"
],
"operationId": "FizzBuzz_Get",
"parameters": [
{
"type": "integer",
"name": "from",
"in": "query",
"required": true,
"format": "int32",
"x-nullable": false
},
{
"type": "integer",
"name": "to",
"in": "query",
"required": true,
"format": "int32",
"x-nullable": false
}
],
"responses": {
"200": {
"x-nullable": true,
"description": "",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
以下の目標をすべて達成することができました。
- DI(依存性の注入)でサービスクラスを注入する
- xUnit を使用した自動テスト
- Swagger を使ったAPI仕様書