/league
」エンドポイントが勝ちの数で順序付けられたプレーヤーを返す必要があると解釈しなかったことに不満を持っています!PlayerStore
抽象化のため、それを別のものと交換するのは簡単です。InMemoryPlayerStore
を保持します。新しい実装が統合テストに合格するのに十分であると確信したら、それを入れ替えてから、InMemoryPlayerStore
を削除します。io.Reader
)、データの書き込み(io.Writer
)、および標準ライブラリを使用してこれらの関数をテストすることなく標準ライブラリをテストする方法について、標準ライブラリに関連するインターフェースに精通しているはずです。実際のファイルを使用する必要があります。PlayerStore
を実装する必要があるため、実装する必要のあるメソッドを呼び出すストアのテストを記述します。GetLeague
から始めます。Reader
を返すstrings.NewReader
を使用しています。 これは、FileSystemPlayerStore
がデータを読み取るために使用するものです。main
では、Reader
でもあるファイルを開きます。FileSystemPlayerStore
を定義しましょうReader
を渡していますが、予期しておらず、まだGetLeague
が定義されていないため、問題があります。league.go
と呼ばれる新しいファイルを作成し、これを中に入れます。server_test.go
のテストヘルパーgetLeagueFromResponse
で呼び出しますio.Reader
の定義を思い出してみましょう。Read
」しようとするとどうなりますか?Reader
が最後に到達したため、これ以上読むものがないことです。最初に戻るように指示する方法が必要です。Reader
と[Seeker
](https://golang.org/pkg/io/#Seeker)で構成されるインターフェースです。FileSystemPlayerStore
を変更できますか?string.NewReader
は、ReadSeeker
も実装しているため、他の変更を行う必要はありませんでした。GetPlayerScore
を実装します。RecordWin
でスコアの記録を開始する必要があります。Writer
を使用しますが、すでにReadSeeker
があります。潜在的に2つの依存関係が存在する可能性がありますが、標準ライブラリにはすでにReadWriteSeeker
用のインターフェースがあり、ファイルで必要なすべてのことを実行できます。strings.Reader
が ReadWriteSeeker
を実装しないことはそれほど驚くべきことではないので、何をしますか?*os.File
はReadWriteSeeker
を実装します。これの長所は、それが統合テストになり、実際にファイルシステムからの読み取りと書き込みを行っているため、非常に高い信頼性が得られることです。短所は、ユニットテストの方が速く、一般にシンプルであるためです。また、一時ファイルを作成し、テスト後にそれらが確実に削除されるように、さらに作業を行う必要があります。strings.Reader
をos.File
で置き換えることにより、他のテストをコンパイルする必要があります。ReadWriteSeeker
(ファイル)だけでなく、関数も返すことに気づくでしょう。 テストが終了したら、ファイルを確実に削除する必要があります。エラーが発生しやすく、読者の興味をそそる可能性があるため、ファイルの詳細をテストに漏らしたくない。removeFile
関数を返すことで、ヘルパーの詳細を処理でき、呼び出し側が実行する必要があるのはdefer cleanDatabase()
を実行することだけです。./file_system_store_test.go:67:8: store.RecordWin undefined (type FileSystemPlayerStore has no field or method RecordWin)
player.Wins++
」ではなく「league[i].Wins++
」をしているのか、疑問に思われるかもしれません。range
」を指定すると、ループの現在のインデックス(この場合はi
)とそのインデックスにある要素の_copy_が返されます。コピーのWins
値を変更しても、繰り返し処理するleague
スライスには影響しません。そのため、league[i]
を実行して実際の値への参照を取得し、代わりにその値を変更する必要があります。GetPlayerScore
とRecordWin
では、名前でプレーヤーを見つけるために[] Player
を繰り返し処理しています。FileSystemStore
の内部でこの共通コードをリファクタリングすることもできますが、私には、これが新しいタイプに引き上げることができるおそらく有用なコードであると感じています。これまで「リーグ"League"
」での作業は常に[]Player
で行っていましたが、League
という新しいタイプを作成できます。これは、他の開発者が理解しやすくなり、そのタイプに便利なメソッドをアタッチして使用できるようになります。league.go
内に以下を追加しますLeague
を持っている場合、彼らは与えられたプレイヤーを簡単に見つけることができます。PlayerStore
インターフェイスを変更して、[]Player
ではなくLeague
を返すようにします。テストを再実行してみてください。インターフェイスを変更したためコンパイルの問題が発生しますが、修正は非常に簡単です。戻り値の型を []Player
からLeague
に変更するだけです。file_system_store
のメソッドを簡略化できます。League
の他の便利な機能をリファクタリングできる方法を見つけることができます。Find
がnil
を返すシナリオを処理する必要があるだけです。Store
を使用してみることができます。これにより、ソフトウェアが動作するという確信が高まり、冗長なInMemoryPlayerStore
を削除できます。TestRecordingWinsAndRetrievingThem
で古いストアを置き換えます。InMemoryPlayerStore
を削除できます。 main.go
にコンパイルの問題が発生し、「実際の」コードで新しいストアを使用するようになります。os.OpenFile
の2番目の引数では、ファイルを開くための権限を定義できます。この場合、O_RDWR
は読み取りと書き込みを行うことを意味し、os.O_CREATE
はファイルが存在しない場合にファイルを作成することを意味します。GetLeague()
またはGetPlayerScore()
を呼び出すたびに、ファイル全体を読み取ってJSONに解析します。FileSystemStore
はリーグの状態に完全に責任があるため、これを行う必要はありません。プログラムの起動時にファイルを読み取るだけで、データが変更されたときにファイルを更新するだけです。FileSystemStore
の値として保存できます。f.league
を使用できます。FileSystemPlayerStore
の初期化について不平を言うので、新しいコンストラクターを呼び出して修正するだけです。RecordWin
を実行すると、ファイルの先頭にSeek
して新しいデータを書き込みますが、新しいデータが以前のデータよりも小さい場合はどうなるでしょうか。Tape
」と呼びます。以下を使用して新しいファイルを作成します。Seek
部分をカプセル化しているため、ここではWrite
のみを実装していることに注意してください。つまり、FileSystemStore
は、代わりにWriter
への参照のみを持つことができます。Tape
を使用するようにコンストラクタを更新しますRecordWin
からSeek
呼び出しを削除することで、私たちが望んでいた驚くべき見返りを得ることができます。はい、あまり感じませんが、少なくとも他の種類の書き込みを行う場合、Write
を使用して必要な動作を実行できることを意味します。さらに、潜在的に問題のあるコードを個別にテストして修正できるようになります。tape
を使用してファイルに書き込み、もう一度読み取って、ファイルの内容を確認します。tape_test.go
os.File
には、ファイルを効率的に空にできる切り捨て関数があります。これを呼び出して、必要なものを取得できます。tape
を次のように変更します。io.ReadWriteSeeker
を想定している多くの場所で失敗しますが、*os.File
で送信しています。これらの問題は自分で修正できるはずですが、行き詰まった場合はソースコードを確認してください。TestTape_Write
テストはパスするはずです!RecordWin
には、json.NewEncoder(f.database).Encode(f.league)
という行があります。Encoder
」への参照を保存し、コンストラクターで初期化します。RecordWin
で使用します。PlayerStore
を単体テストするための最も簡単な方法であったため、io.Reader
を使用してコードを開始しました。コードを開発したとき、io.ReadWriter
に移動し、次にio.ReadWriteSeeker
に移動しました。次に、標準ライブラリには、*os.File
以外に実際にそれを実装したものは何もないことがわかりました。独自に作成するか、オープンソースを使用するかを決定することもできましたが、テスト用の一時ファイルを作成するだけで実用的でした。*os.File
にもあるTruncate
が必要でした。これらの要件を取り込んだ独自のインターフェースを作成することはオプションでした。*os.File
以外のタイプを取ることは非現実的であるため、インターフェイスが提供するポリモーフィズムは必要ありません。FileSystemStore.go
に戻ると、コンストラクターにleague, _ := NewLeague(f.database)
があります。io.Reader
からリーグを解析できない場合、NewLeague
はエラーを返す可能性があります。