Open8

TDDBC Sendai X Online の課題をC#でやってみる

ピン留めされたアイテム
髙野 将髙野 将

いちごの格付け

お題

TDDBC Sendai X Online お題
https://gist.github.com/135yshr/3a894104e63074ae307e6c26867a6e54

今回のお題について

いちごは、種の段階から品種が決まっており、成長するとサイズが大きくなり、赤く色づいてくると出荷できるようになります。
農家さんは、出荷する際、できるだけ大きくて見栄えの良いいちごを選びます。見栄えによって出荷時の値段が変わってくるからです。

GitHub

masaru-b-cl/TddbcSendaiXByCSharp: TDDBC Sendai X Online のお題をC#でやってみた
https://github.com/masaru-b-cl/TddbcSendaiXByCSharp

髙野 将髙野 将

お題

いちごを出荷するには、品種とサイズを知る必要があります。

品種とサイズを与えて、いちごを作成し、その文字列表現を取得しましょう。

文字列表現の例: あまおう: S とちおとめ: M もういっこ: L

なお、品種は、 あまおう とちおとめ もういっこ の3種類、いちごのサイズは S M L LL の4種類です。

髙野 将髙野 将

TODOリストの作成

https://github.com/masaru-b-cl/TddbcSendaiXByCSharp/pull/1

まず、下記の文の太字部分に着目しましょう。

品種とサイズを与えて、いちごを作成し、その文字列表現を取得しましょう。

「品種」と「サイズ」という言葉が出ましたね。また「作成」とありますので、まずはこの部分だけを切り出して、TODOリストに追加します。

- [ ] 品種とサイズを与えて、いちごを作成する

次に、先程の文の後半に着目しましょう。

品種とサイズを与えて、いちごを作成し、その文字列表現を取得しましょう。

「その」は「いちごの」と置き換えられます。そして「文字列表現を取得」するためには「文字列表現に変換」できないといけないということなので、次のようにTODOリストに付け加えます。

- [ ] 品種とサイズを与えて、いちごを作成する
- [ ] いちごを文字列表現に変換する

次に、下記の部分に着目します。

文字列表現の例: あまおう: S とちおとめ: M もういっこ: L

この箇所により、文字列表現の形式が<品種>: <サイズ>であることがわかります。こちらを「いちごを文字列表現に変換する」の項目の下に具体例とともにぶら下げます。

- [ ] 品種とサイズを与えて、いちごを作成する
- [ ] いちごを文字列表現に変換する
    - [ ] "<品種>: <サイズ>"形式になる
        - [ ] 「あまおう」の「S」サイズは"あまおう: S"になる

今度は最後の文に着目します。

なお、品種は、 あまおう とちおとめ もういっこ の3種類いちごのサイズは S M L LL の4種類です。

この文により「品種」、「サイズ」に与えることが可能な値の範囲が分かりますので、それぞれTODOリストに追加します。

- [ ] 品種とサイズを与えて、いちごを作成する
    - [ ] いちごの品種
        - [ ] あまおう
        - [ ] とちおとめ
        - [ ] もういっこ
    - [ ] いちごのサイズ
        - [ ] S
        - [ ] M
        - [ ] L
        - [ ] LL
- [ ] いちごを文字列表現に変換する
    - [ ] "<品種>: <サイズ>"形式になる
        - [ ] 「あまおう」の「S」サイズは"あまおう: S"になる

ただし、提示された値以外を与えたらどうなるか、仕様には明記されていません。例えばnullや空文字などです。

こういった場合は、仕様策定者に確認をしましょう。その結果、ここでは無効な引数であるとして、例外を発生させることになりました。これもTODOリストに追加します。

- [ ] 品種とサイズを与えて、いちごを作成する
    - [ ] いちごの品種
        - [ ] あまおう
        - [ ] とちおとめ
        - [ ] もういっこ
        - [ ] 想定外の値は例外を発生させる
    - [ ] いちごのサイズ
        - [ ] S
        - [ ] M
        - [ ] L
        - [ ] LL
        - [ ] 想定外の値は例外を発生させる
- [ ] いちごを文字列表現に変換する
    - [ ] "<品種>: <サイズ>"形式になる
        - [ ] 「あまおう」の「S」サイズは"あまおう: S"になる

これで、一旦TODOリストを完成とします。

髙野 将髙野 将

最初のテストを書くその前に

https://github.com/masaru-b-cl/TddbcSendaiXByCSharp/pull/2

それではいよいよコードを書き始めましょう。まずは、最初のテストを書くことを目標に始めます。

が、「最初のテストを書く」といっても、実はその前にやることがたくさんあります。順に見ていきましょう。

プロダクトの名付け

まず、そもそもこのプロダクトの名前を決めるところから始めないといけません。今回のお題は「いちごの格付け」です。「いちご」は英語でStrawberryですが、長くてタイプしにくいですし、単にIchigoにしてしまいましょう。そして「格付け」の部分は「ランクを計算する」とみなし、RankCalculatorという言葉を使うことにして、今回はIchigoRankCalculatorにしましょう。

ワークスペース作成

次に、コードのワークスペースを作成します。今回はC#ですので、.NET SDKdotnetcliを使いましょう。

dotnetcliでは、何かを新たに作成するためにnewコマンドを使います。何も指定せずに実行すると、作成できるものの一覧が表示されます。

$ dotnet new
Templates                                     Short Name      Language    Tags
--------------------------------------------  --------------  ----------  ----------------------
Console Application                           console         [C#],F#,VB  Common/Console
Class library                                 classlib        [C#],F#,VB  Common/Library
Worker Service                                worker          [C#],F#     Common/Worker/Web
Unit Test Project                             mstest          [C#],F#,VB  Test/MSTest
NUnit 3 Test Project                          nunit           [C#],F#,VB  Test/NUnit
NUnit 3 Test Item                             nunit-test      [C#],F#,VB  Test/NUnit
xUnit Test Project                            xunit           [C#],F#,VB  Test/xUnit
Razor Component                               razorcomponent  [C#]        Web/ASP.NET
Razor Page                                    page            [C#]        Web/ASP.NET
MVC ViewImports                               viewimports     [C#]        Web/ASP.NET
MVC ViewStart                                 viewstart       [C#]        Web/ASP.NET
Blazor Server App                             blazorserver    [C#]        Web/Blazor
Blazor WebAssembly App                        blazorwasm      [C#]        Web/Blazor/WebAssembly
ASP.NET Core Empty                            web             [C#],F#     Web/Empty
ASP.NET Core Web App (Model-View-Controller)  mvc             [C#],F#     Web/MVC
ASP.NET Core Web App                          webapp          [C#]        Web/MVC/Razor Pages
ASP.NET Core with Angular                     angular         [C#]        Web/MVC/SPA
ASP.NET Core with React.js                    react           [C#]        Web/MVC/SPA
ASP.NET Core with React.js and Redux          reactredux      [C#]        Web/MVC/SPA
Razor Class Library                           razorclasslib   [C#]        Web/Razor/Library
ASP.NET Core Web API                          webapi          [C#],F#     Web/WebAPI
ASP.NET Core gRPC Service                     grpc            [C#]        Web/gRPC
dotnet gitignore file                         gitignore                   Config
global.json file                              globaljson                  Config
NuGet Config                                  nugetconfig                 Config
Dotnet local tool manifest file               tool-manifest               Config
Web Config                                    webconfig                   Config
Solution File                                 sln                         Solution
Protocol Buffer File                          proto                       Web/gRPC

Examples:
    dotnet new mvc --auth Individual
    dotnet new react --auth Individual
    dotnet new --help
    dotnet new gitignore --help

まずはソリューションの作成です。上記の出力結果を見ると、テンプレートに「Solution File」というものがあります。

Solution File                                 sln                         Solution

よって、その「Short Name」であるslnを指定して実行します。

$ dotnet new sln -o IchigoRankCalculator
The template "Solution File" was created successfully.

コマンドを実行すると、次のようにディレクトリとslnファイルが作成されます。

$ tree IchigoRankCalculator
IchigoRankCalculator
└── IchigoRankCalculator.sln

0 directories, 1 file

以降の作業は作成したソリューションディレクトリに移動して続けましょう。

$ cd IchigoRankCalculator

テストプロジェクト作成

次に、テストプロジェクトを作成しましょう。今回はテスティングフレームワークにxUnit.netを用います。

先程見たdotnet newコマンドのテンプレート一覧を見ると、xUnitを使ったテストプロジェクトはxunitコマンドで作成できることがわかります。

xUnit Test Project                            xunit           [C#],F#,VB  Test/xUnit

C#のコードでは、テストプロジェクトは<プロダクト名>.Test形式で名付けられることが多いので、ここでもそれに倣ってIchigoRankCalculator.Testという名前で作成しましょう。

$ dotnet new xunit -o IchigoRankCalculator.Test
The template "xUnit Test Project" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on IchigoRankCalculator.Test/IchigoRankCalculator.Test.csproj...
  復元対象のプロジェクトを決定しています...
  /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/IchigoRankCalculator.Test.csproj を復元しました (957 ms)。
Restore succeeded.
$ tree .
.
├── IchigoRankCalculator.Test
│   ├── IchigoRankCalculator.Test.csproj
│   ├── UnitTest1.cs
│   └── obj
│       ├── IchigoRankCalculator.Test.csproj.nuget.dgspec.json
│       ├── IchigoRankCalculator.Test.csproj.nuget.g.props
│       ├── IchigoRankCalculator.Test.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
└── IchigoRankCalculator.sln

作成したテストプロジェクトは、dotnet sln addコマンドでソリューションに追加しておきます。

$ dotnet sln add IchigoRankCalculator.Test
プロジェクト `IchigoRankCalculator.Test/IchigoRankCalculator.Test.csproj` をソリューションに追加しました。

ここまででテストプロジェクトの作成がうまくできているか確認するため、一度テストを実行しておきましょう。

テストを実行するには、dotnet testコマンドを実行します。

$ dotnet test
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     1、スキップ:     0、合計:     1、期間: 2 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

無事、テストが実行できることを確認しました。

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

テストが実行できたところで、いよいよプロダクトコードを書くためのクラスライブラリプロジェクトを作成します。

なお、.NETのプログラムでは、実行形式であるアプリプロジェクトを対象にテストを書くことも可能です。しかし、あくまでテスト対象はライブラリとして作成し、アプリプロジェクトではこのライブラリを参照して実行するような形にしたほうが良いでしょう。

クラスライブラリプロジェクトを作成するには、dotnet newコマンドにclasslibを指定します。

Class library                                 classlib        [C#],F#,VB  Common/Library
$ dotnet new classlib -o IchigoRankCalculator
The template "Class library" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on IchigoRankCalculator/IchigoRankCalculator.csproj...
  復元対象のプロジェクトを決定しています...
  /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/IchigoRankCalculator.csproj を復元しました (60 ms)。
Restore succeeded.

先程のテストプロジェクトと同様にソリューションに追加しておきます。

$ dotnet sln add IchigoRankCalculator
プロジェクト `IchigoRankCalculator/IchigoRankCalculator.csproj` をソリューションに追加しました。

また、テストプロジェクトに対しても、クラスライブラリプロジェクトへの参照を追加しておきましょう。これには、dotnet add <テストプロジェクト> reference <クラスライブラリプロジェクト>コマンドを使います。

$ dotnet add IchigoRankCalculator.Test reference IchigoRankCalculator
参照 `..\IchigoRankCalculator\IchigoRankCalculator.csproj` がプロジェクトに追加されました。

これでようやく最初のテストを書く準備ができました。

$ tree .
.
├── IchigoRankCalculator
│   ├── Class1.cs
│   ├── IchigoRankCalculator.csproj
│   └── obj
│       ├── IchigoRankCalculator.csproj.nuget.dgspec.json
│       ├── IchigoRankCalculator.csproj.nuget.g.props
│       ├── IchigoRankCalculator.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
├── IchigoRankCalculator.Test
│   ├── IchigoRankCalculator.Test.csproj
│   ├── UnitTest1.cs
│   └── obj
│       ├── IchigoRankCalculator.Test.csproj.nuget.dgspec.json
│       ├── IchigoRankCalculator.Test.csproj.nuget.g.props
│       ├── IchigoRankCalculator.Test.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
└── IchigoRankCalculator.sln
髙野 将髙野 将

最初のテストを作成する

https://github.com/masaru-b-cl/TddbcSendaiXByCSharp/pull/5

準備が整ったところで最初のテストを書いていきましょう。

取り組むTODOの選択

まずは作成したTODOリストから、最初に手を付けるべき項目を考えます。

一番やりやすいのは次の項目です。

- [ ] 品種とサイズを与えて、いちごを作成する

ただ、このままではまだコードを書くには曖昧すぎることがわかります。「品種」と「サイズ」をどういう形式で扱うのかをまだ決めていなかったためです。

そこで、ここでは1番かんたんな「文字列」として、品種とサイズを扱うことにしましょう。ですので、最初に作成するテストのために、次のようにサブタスクを追加します。

- [ ] 品種とサイズを与えて、いちごを作成する
    - [ ] 品種に"あまおう"、サイズに"S"を与えて、いちごを作成する

最初のテストコード

追加したTODOを元に、最初のテストコードを作成します。

先程テストプロジェクトを生成した際、UnitTest1.csというファイルが既に作成されています。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {

        }
    }
}

まずは、このファイルの名前を変えましょう。今回は単純にIchigoRankCalculatorTest.csとでもしておきましょう。

$ mv IchigoRankCalculator.Test/UnitTest1.cs IchigoRankCalculator.Test/IchigoRankCalculatorTest.cs

次にテストクラス名を変更します。C#はクラス名に日本語が使えるので、とりあえず「いちごの作成」と名前をつけます。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void Test1()
        {

        }
    }
}

次に、最初に選んだTODOをもとにテストメソッド名をつけます。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {

        }
    }
}

TDDはアサートファーストが原則なので、アサーションから書いていきます。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {
            Assert.NotNull(ichigo);
        }
    }
}

ichigoIchigo型のクラスのインスタンスであるとして、new演算子で作成しましょう。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {
            Assert.NotNull(ichigo);
        }
    }
}

ただ、このままではIchigoクラスがありませんのでコンパイルエラーのままです。そこで、クラスライブラリプロジェクト内に生成されたClass1.csファイル

using System;

namespace IchigoRankCalculator
{
    public class Class1
    {
    }
}

Ichigo.csにリネームし、クラス名もClass1からIchigoに変更しましょう。

using System;

namespace IchigoRankCalculator
{
    public class Ichigo
    {
    }
}

そして、dotnet testコマンドを使ってテストを実行して、無事成功しました。

  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     1、スキップ:     0、合計:     1、期間: 2 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)
髙野 将髙野 将

品種とサイズを与えて、いちごを作成する

https://github.com/masaru-b-cl/TddbcSendaiXByCSharp/pull/6

いちごが作成できるようになったので、次は「品種」と「サイズ」を与えて作成できるようにしましょう。

コードを修正する前に、「品種」をコード上なんと表現するか考えないといけません。

英辞郎 on the WEBで「品種」を検索すると「breed」という単語が出てきます。

breedの意味・使い方・読み方|英辞郎 on the WEB

  1. 《生物》〔動植物の〕種族、品種◆人間が交配して作る種を指すことが多い。

そこで、今回は「品種」は「breed」という単語で表すことにしましょう。

TDDですので、まずはテストを変更します。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {
            Ichigo ichigo = new Ichigo(breed: "あまおう", size: "S");

            // Assert.NotNull(ichigo);
            Assert.Equal("あまおう", ichigo.Breed);
            Assert.Equal("S", ichigo.Size);
        }
    }
}

このテストが通るように、プロダクトコードを修正します。まず、「仮実装」でやっていきます。

using System;

namespace IchigoRankCalculator
{
    public class Ichigo
    {
        public Ichigo(string breed, string size)
        {
        }

        public string Breed => "あまおう";

        public string Size => "S";
    }
}

テストが通ることを確認しましょう。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln 
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     1、スキップ:     0、合計:     1、期間: 2 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

続いて、仮実装を本実装を進めるため、もう一つテストを追加し、三角測量を行います。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {
            Ichigo ichigo = new Ichigo(breed: "あまおう", size: "S");

            // Assert.NotNull(ichigo);
            Assert.Equal("あまおう", ichigo.Breed);
            Assert.Equal("S", ichigo.Size);
        }

        // ↓追加したテスト
        [Fact]
        public void 品種にとちおとめ_サイズにMを与えていちごを作成する()
        {
            Ichigo ichigo = new Ichigo(breed: "とちおとめ", size: "M");

            // Assert.NotNull(ichigo);
            Assert.Equal("とちおとめ", ichigo.Breed);
            Assert.Equal("M", ichigo.Size);
        }
    }
}

テストを実行し、追加したテストが「想定通りに失敗すること」を確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。
[xUnit.net 00:00:00.66]     IchigoRankCalculator.Test.いちごの作成.品種にとちおとめ_サイズにMを与えていちごを作成する [FAIL]
  失敗 IchigoRankCalculator.Test.いちごの作成.品種にとちおとめ_サイズにMを与えていちごを作成する [3 ms]
  エラー メッセージ:
   Assert.Equal() Failure
          ↓ (pos 0)
Expected: とちおとめ
Actual:   あまおう
          ↑ (pos 0)
  スタック トレース:
     at IchigoRankCalculator.Test.いちごの作成.品種にとちおとめ_サイズにMを与えていちごを作成する() in /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/IchigoRankCalculatorTest.cs:line 24

失敗!   -失敗:     1、合格:     1、スキップ:     0、合計:     2、期間: 4 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

この失敗を成功させるよう、本実装を行います。BreedSizeget飲み可能なプロパティにして、コンストラクタ引数の値をセットするように変更します。

using System;

namespace IchigoRankCalculator
{
    public class Ichigo
    {
        public Ichigo(string breed, string size)
        {
            this.Breed = breed;
            this.Size = size;
        }

        public string Breed { get; }

        public string Size { get; }
    }
}

そして、テストを実行して問題ないことを確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln                                                              
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     2、スキップ:     0、合計:     2、期間: 3 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

テストが成功したところで、テストコードのリファクタリングを行います。といっても、ここでは不要になったコメントアウトされたアサーションを削除するくらいにとどめておきましょう。

using System;
using Xunit;

namespace IchigoRankCalculator.Test
{
    public class いちごの作成
    {
        [Fact]
        public void 品種にあまおう_サイズにSを与えていちごを作成する()
        {
            Ichigo ichigo = new Ichigo(breed: "あまおう", size: "S");

            Assert.Equal("あまおう", ichigo.Breed);
            Assert.Equal("S", ichigo.Size);
        }

        [Fact]
        public void 品種にとちおとめ_サイズにMを与えていちごを作成する()
        {
            Ichigo ichigo = new Ichigo(breed: "とちおとめ", size: "M");

            Assert.Equal("とちおとめ", ichigo.Breed);
            Assert.Equal("M", ichigo.Size);
        }
    }
}

ここまでで完了したTODOを反映しておきます。

- [ ] 品種とサイズを与えて、いちごを作成する
    - [x] ~~品種に"あまおう"、サイズに"S"を与えて、いちごを作成する~~
    - [ ] いちごの品種
        - [x] あまおう
        - [ ] とちおとめ
        - [ ] もういっこ
        - [ ] 想定外の値は例外を発生させる
    - [ ] いちごのサイズ
        - [x] S
        - [x] M
        - [ ] L
        - [ ] LL
        - [ ] 想定外の値は例外を発生させる
- [ ] いちごを文字列表現に変換する
    - [ ] "<品種>: <サイズ>"形式になる
        - [ ] 「あまおう」の「S」サイズは"あまおう: S"になる
髙野 将髙野 将

いちごを文字列表現に変換する

https://github.com/masaru-b-cl/TddbcSendaiXByCSharp/pull/8

次は

- [ ] いちごを文字列表現に変換する
    - [ ] "<品種>: <サイズ>"形式になる
        - [ ] 「あまおう」の「S」サイズは"あまおう: S"になる

のTODOに取り組んでいきましょう。

最初はテストからです。これまで作成した2つのテストとは別に作成しましょう。

    public class いちごを文字列表現に変換する
    {
        [Fact(DisplayName = "あまおうのSサイズは\"あまおう: S\"になる")]
        public void あまおうのSサイズ()
        {
            var ichigo = new Ichigo(breed: "あまおう", size: "S");
            Assert.Equal("あまおう: S", ichigo.ToString());
        }
    }

まず、先程とは別観点のテストなので、テストクラスも新たに作成します。

次に、テストメソッドを追加しますが、今回のテストは期待値として「"あまおう: S"」があるので、テストメソッドでもそれを表現したいです。そんなとき、xUnit.netでは、Fact属性のDisplayName引数でテストの表示名を指定できますので、それを利用します。

そして、「いちごを文字列表現に変換する」のですから、とりあえず最もシンプルなToString()メソッドを使って変換させることにしましょう。

テストを実行して、失敗することを確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln        
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。
[xUnit.net 00:00:00.52]     あまおうのSサイズは"あまおう: S"になる [FAIL]
  失敗 あまおうのSサイズは"あまおう: S"になる [3 ms]
  エラー メッセージ:
   Assert.Equal() Failure
          ↓ (pos 0)
Expected: あまおう: S
Actual:   IchigoRankCalculator.Ichigo
          ↑ (pos 0)
  スタック トレース:
     at IchigoRankCalculator.Test.いちごを文字列表現に変換する.あまおうのSサイズ() in /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/IchigoRankCalculatorTest.cs:line 33

失敗!   -失敗:     1、合格:     2、スキップ:     0、合計:     3、期間: 6 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

想定通り失敗し、テスト名にもFact属性のDisplayName引数で指定した「あまおうのSサイズは"あまおう: S"になる」が表示されているのを確認できます。

このテストを通すように、まずは仮実装です。ToString()メソッドをオーバーライドして、リテラル文字列"あまおう: S"を返すようにしましょう。

using System;

namespace IchigoRankCalculator
{
    public class Ichigo
    {
        public Ichigo(string breed, string size)
        {
            Breed = breed;
            Size = size;
        }

        public string Breed { get; }
        public string Size { get; }

        public override string ToString()
        {
            return "あまおう: S";
        }
    }
}

テストを実行して成功することを確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     3、スキップ:     0、合計:     3、期間: 6 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

今度はテストが通るまま、本実装を進めていきましょう。まずはいちごの「品種」を埋め込んでみましょう。$"〜"を用いた「文字列補間(string interpolation)」を使ってやっていきます。

        public override string ToString()
        {
            return $"{Breed}: S";
        }

テストを実行し、通ったままなことを確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     3、スキップ:     0、合計:     3、期間: 6 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

つづいて、今度は「サイズ」を埋め込みましょう。「品種」と同様に対応します。

        public override string ToString()
        {
            return $"{Breed}: {Size}";
        }

テストを実行して確認します。

$ dotnet test IchigoRankCalculator/IchigoRankCalculator.sln   
  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  IchigoRankCalculator -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator/bin/Debug/net5.0/IchigoRankCalculator.dll
  IchigoRankCalculator.Test -> /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll
/Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (.NETCoreApp,Version=v5.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 16.9.0
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     3、スキップ:     0、合計:     3、期間: 6 ms - /Users/takano.sho/dotnetproj/TddbcSendaiXByCSharp/IchigoRankCalculator/IchigoRankCalculator.Test/bin/Debug/net5.0/IchigoRankCalculator.Test.dll (net5.0)

これでTODOを完了にできます。

- [ ] 品種とサイズを与えて、いちごを作成する
    - [x] 品種に"あまおう"、サイズに"S"を与えて、いちごを作成する
    - [ ] いちごの品種
        - [x] あまおう
        - [ ] とちおとめ
        - [ ] もういっこ
        - [ ] 想定外の値は例外を発生させる
    - [ ] いちごのサイズ
        - [x] S
        - [x] M
        - [ ] L
        - [ ] LL
        - [ ] 想定外の値は例外を発生させる
- [x] いちごを文字列表現に変換する
    - [x] "<品種>: <サイズ>"形式になる
        - [x] 「あまおう」の「S」サイズは"あまおう: S"になる