Hello, World
Hello, World
- 好きな場所にフォルダを作成します。
- その中に
hello.go
という名前の新しいファイルを作り、その中に以下のコードを記述します。
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
次のように実行します。
go run hello.go
Goでプログラムを作成すると、その中に
main
関数が定義されたmain
パッケージが作成されます。 パッケージは、関連するGoコードをグループ化する方法です。func
キーワードは、名前と本体で関数を定義する方法です。import "fmt"
では、印刷に使用する Println
関数を含むパッケージをインポートしています。どのようにテストすればよいと思いますか? 「ドメイン」コードを外界から分離することは良いことです。
fmt.Println
は副作用であり、送信する文字列はドメインです。テストを簡単にするために、これらの懸念事項を分離しましょう
package main
import "fmt"
func Hello() string {
return "Hello, world"
}
func main() {
fmt.Println(Hello())
}
func
を使用して新しい関数を再度作成しましたが、今回は定 義に別のキーワードstring
を追加しました。 つまり、この関数は string
を返します。ここで、
hello_test.go
という新しいファイルを作成します。ここで、Hello
関数のテストを記述しますpackage main
import "testing"
func TestHello(t *testing.T) {
got := Hello()
want := "Hello, world"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
次は、テストを実行します。ターミナルで
go test
と入力してください。テストが成功したら、おそらく以前のバージョンの Go を使っているのでしょう。しかし、Go 1.16 以降を使っている場合は、テストがまったく実行されない可能性があります。その代わり、ターミナルに次のようなエラーメッセージが表示されます。$ go test
go: cannot find main module; see 'go help modules'
何が問題なのか?一言で言えば、モジュールです。幸いなことに、この問題は簡単に解決することができます。ターミナルで
go mod init hello
と入力してください。そうすると、次のような内容の新しいファイルが作成されます。module hello
go 1.16
このファイルは
go
ツールに、あなたのコードに関する重要な情報を伝えます。もし、アプリケーションを配布する予定があるなら、コードがダウンロードできる場所や、依存関係の情報も含めるでしょう。今のところ、モジュールファイルは最小限のものであり、そのままにしておいても構いません。モジュールについて詳しく知りたいなら、Golangドキュメントのリファレンスを参照してください。これでGo 1.16 でもテストは実行されるはずなので、テストと Go の学習に戻ることができます。今後の章では、
go test
や go build
といったコマンドを実行する前に、それぞれの新しいフォルダで go mod init SOMENAME
を実行する必要があります。ターミナルで
go test
を実行します。合格したはずです! 確認するために、want
文字列を変更して、意図的にテストを中断してみてください。複数のテストフレームワークを選択する必要がなく、インストール方法を理解する必要がないことに注意してください。 必要なものはすべて言語に組み込まれており、構文は、これから記述する残りのコードと同じです。
テストの作成は、関数の作成と同様であり、いくつかのルールがあります。
xxx_test.go
のような名前のファイルにある必要があります。- テスト関数は
Test
という単語で始まる必要があります。 - テスト関数は1つの引数のみをとります。
t *testing.T
*testing.T
型を使うには、他のファイルのfmt
と同じようにimport "testing"
が必要です。
とりあえず、
* testing.T
タイプのt
がテストフレームワークへのhook
(フック)であることを知っていれば十分なので、失敗したいときに t.Fail()
のようなことを実行できます。新しいトピックをいくつか取り上げました。
Goのステートメントが他のプログラミング言語とよく似ている場合。
いくつかの変数を構文
varName := value
で宣言しています。これにより、読みやすくするためにテストでいくつかの値を再利用できます。メッセージを出力してテストに失敗する
t
でErrorf
method を呼び出しています。 f
は、プレースホルダー値%q
に値が挿入された文字列を作成できる形式を表します。 テストを失敗させたとき、それがどのように機能するかは明らかです。メソッドと関数の違いについては後で説明します。
Goのもう1つの質機能はドキュメントです。
godoc -http :8000
を実行すると、ローカルでドキュメントを起動できます。 localhost:8000/pkg に移動すると、システムにインストールされているすべてのパッケージが表示されます。godoc
コマンドがない場合は、godoc
を含まない新しいバージョンのGo(1.14
以降)を使用している可能性があります no longer including godoc
。 go get golang.org/x/tools/cmd/godoc
を使用して手動でインストールできます。これでテストが完了したので、ソフトウェアを安全に反復できます。
最後の例では、テストを記述した後、コードを記述したので、テストを記述して関数を宣言する方法の例を取得できます。この時点から、
テストを最初に作成
します。次の要件は、挨拶の受信者を指定できるようにすることです。
これらの要件をテストに取り込むことから始めましょう。 これは基本的なテスト主導の開発であり、テストが希望どおりに実際にテストされていることを確認できます。テストをさかのぼって作成すると、コードが意図したとおりに機能しなくても、テストが引き続きパスする可能性があります。
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
go test
を実行すると、コンパイルエラーが発生するはずです./hello_test.go:6:18: too many arguments in call to Hello
have (string)
want ()
Goのような静的に型付けされた言語を使用する場合、コンパイラーをリッスンすることが重要です。 コンパイラーは、コードがどのようにスナップして機能するかを理解しているので、そうする必要はありません。
この場合、コンパイラーは続行するために何をする必要があるかを指示しています。引数を受け付けるには、関数
Hello
を変更する必要があります。文字列型の引数を受け入れるように
Hello
関数を編集しますfunc Hello(name string) string {
return "Hello, world"
}
もう一度テストを実行すると、引数を渡していないため、
hello.go
はコンパイルに失敗します。コンパイルできるように"world"
を渡してください。func main() {
fmt.Println(Hello("world"))
}
テストを実行すると、次のように表示されます。
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''
ようやくコンパイルプログラムができましたが、テストによると要件を満たしていません。
name
引数を使用してテストに合格し、 Hello,
と連結してみましょうfunc Hello(name string) string {
return "Hello, " + name
}
テストを実行すると、テストに合格するはずです。通常、TDDサイクルの一部として、 refactor を実行する必要があります。
この時点で、ソース管理を使用している場合はそうする必要があります!。 コードをそのまま
commit
します。テストに裏打ちされた実用的なソ フトウェアがあります。ただし、次にリファクタリングする予定なので、マスターにプッシュしません。何らかの理由でリファクタリングで混乱に陥った場合に備えて、この時点でコミットすると便利です。いつでも作業バージョンに戻ることができます。
ここでリファクタリングすることは多くありませんが、 constants という別の言語機能を導入できます。
定数は次のように定義できます。
const englishHelloPrefix = "Hello, "
コードをリファクタリングできるようになりました。
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
return englishHelloPrefix + name
}
リファクタリング後、テストを再実行して、何も壊れていないことを確認します。
定数は、
Hello
が呼び出されるたびに"Hello、"
文字列インスタンスを作成する手間を省くため、アプリケーションのパフォーマンスを向上させるはずです。明確にするために、この例ではパフォーマンスの向上はごくわずかです。ただし、値の意味を把握するために、また場合によってはパフォーマンスを支援するために定数を作成することを検討する価値があり ます。
次の要件は、関数が空の文字列で呼び出されたときに、デフォルトで
"Hello、"
ではなく"Hello、World"
を出力することです。新しい失敗するテストを書くことから始めます。
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
}
ここでは、テストの武器であるサブテストに別のツールを導入しています。 「もの」を中心にテストをグループ化し、さまざまなシナリオを説明するサブテストを作成すると便利な場合があります。
このアプローチの利点は、他のテストで使用できる共有コードを設定できることです。
メッセージが期待どおりかどうかを確認するときにコードが繰り返されます。
リファクタリングは、量産コードにとって
ちょうど
ではありません!テストでは、コードが何をする必要があるのかを明確に指定することが重要です。
テストをリファクタリングすることができます。
func TestHello(t *testing.T) {
assertCorrectMessage := func(t testing.TB, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'World'", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
}
さて、ここで何をしましたか?
アサーションを関数にリファクタリングしました。 これにより、重複が削減され、テストの可読性が向上します。 Goでは、他の関数内で関数を宣言して、変数に割り当てることができます。 その後、通常の関数と同じようにそれらを呼び出すことができます。
t * testing.T
を渡す必要があるので、必要なときにテストコードを失敗させることができます。ヘルパー関数については、
*testing.T
と *testing.B
の両方が満たすインターフェースである testing.TB
を受け入れると、テストやベンチマークからヘルパー関数を呼び出すことができるため、便利です。t.Helper()
は、このメソッドがヘルパーであることをテストスイートに伝えるために必要です。こうすることで、テストが失敗したときに報告される行番号は、テストヘルパーの中ではなく 呼び出された関数 の中を示します。これにより、他の開発者が問題を追跡しやすくなります。それでも理解できない場合は、コメントアウトし、テストを失敗させて、テスト出力を観察してください。Goのコメントは、コードに追加情報 を加えるのに最適な方法です。この場合は、コンパイラに特定の行を無視するように指示する手っ取り早い方法です。t.Helper()
のコードをコメントアウトするには、行頭に2つのフォワードスラッシュ //
を追加してください。その行がグレーになるか、他のコードとは別の色に変わることで、コメントアウトされたことがわかるはずです。うまく書かれた不合格のテストができたので、
if
を使用してコードを修正しましょう。const englishHelloPrefix = "Hello, "
func Hello(name string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
テストを実行すると、新しい要件を満たし、他の機能を誤って壊していないことがわかります。
さて、前のコミットを修正するコードに満足しているので、コードの素敵なバージョンをテストでチェックインするだけです。
サイクルをもう一度見てみましょう
- テストを書く
- コンパイラーをパスする
- テストを実行し、失敗することを確認し、エラーメッセージが意味があることを確認します
- テストに合格するのに十分なコードを記述します
- リファクタリング
一見面倒に見えるかもしれませんが、フィードバックループを守ることが重要です。
これは、
関連するテスト
があることを保証するだけでなく、テストの安全性を考慮してリファクタリングすることにより、優れたソフトウェアを設計する
ことを保証するのに役立ちます。テストが失敗したことを確認することは、エラーメッセージがどのように表示されるかを確認できるため、重要なチェックです。開発者としては、テストに失敗して問題が何であるかについて明確なアイデアが得られない場合、コードベースを操作するのは非常に困難です。
テストが
fast
であることを確認し、テストを簡単に実行できるようにツールを設定することで、コードを記述するときにフローの状態に入ることができます。テストを記述しないことにより、フローの状態を壊すソフトウェアを実行して手動でコードをチェックすることを約束し、特に長期的には、時間を節約できなくなります。
よかった、もっと要件があります。 次に、挨拶の言語を指定する2番目のパラメーターをサポートする必要があります。認識されない言語が渡された場合は、デフォルトで英語に設定されます。
TDDを使用してこの機能を簡単に具体化できると確信しているはずです。
スペイン語で合格するユーザーのテストを作成します。既存のスイートに追加します。
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
不正行為をしないことを忘れないでください!
最初にテスト
。テストを実行しようとすると、1つではなく2つの引数を指定して Hello
を呼び出すため、コンパイラは文句を言うべきです_。./hello_test.go:27:19: too many arguments in call to Hello
have (string, string)
want (string)
Hello
に別の文字列引数を追加して、コンパイルの問題を修正しますfunc Hello(name string, language string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
テストをもう一度実行すると、他のテストと
hello.go
でHello
に十分な引数を渡さないというメッセージが表示されます。./hello.go:15:19: not enough arguments in call to Hello
have (string)
want (string, string)
空の文字列を渡すことで修正します。これで、新しいシナリオを除いて、すべてのテストで
and
がコンパイルされます。hello_test.go:29: got 'Hello, Elodie' want 'Hola, Elodie'
ここで
if
を使用して、言語が"Spanish"
に等しいことを確認し、そうであればメッセージを変更しますfunc Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == "Spanish" {
return "Hola, " + name
}
return englishHelloPrefix + name
}
これでテストに合格するはずです。
さて、リファクタリングのお時間です。コードにいくつかの問題が見られるはずです。
"magic"
文字列は、その一部が繰り返されます。自分で試してリファクタリングしてください。変更を加えるたびに、テストを再実行して、リファクタリングが何も壊していないことを確認してください。const spanish = "Spanish"
const englishHelloPrefix = "Hello, "
const spanishHelloPrefix = "Hola, "
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
return englishHelloPrefix + name
}
"French"
を渡すと、"Bonjour、"
が得られることを表明するテストを作成します- それが失敗するのを見て、エラーメッセージが読みやすいことを確認してください
- コードに最小限の合理的な変更を加える
大体このようなものを書いたかもしれません。
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
if language == french {
return frenchHelloPrefix + name
}
return englishHelloPrefix + name
}
特定の値をチェックする多くの
if
ステートメントがある場合、代わりにswitch
ステートメントを使用するのが一般的です。後で言語サポートを追加したい場合は、 switch
を使用してコードをリファクタリングして読みやすくし、拡張性を高めることができます。func Hello(name string, language string) string {
if name == "" {
name = "World"
}
prefix := englishHelloPrefix
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
}
return prefix + name
}
選択した言語で挨拶を含めるためのテストを作成すると、
amazing
関数を拡張するのがいかに簡単かがわかります。多分私たちの機能が少し大きくなっていると主張することができます。このための最も簡単なリファクタリングは、一部の機能を別の関数に抽出することです。
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return greetingPrefix(language) + name
}
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
いくつかの新しい概念
- 関数のシグネチャでは、 named return value
(prefix string)
を作成しました。 - これにより、関数に
prefix
という変数が作成されます。- "zero" 値が割り当てられます。これはタイプによって異なります。たとえば、
int
は0
で、string
の場合は""
です。return prefix
ではなくreturn
を呼び出すだけで、設定されているものを返すことができます。
- これは関数のGo Docに表示されるので、コードの意図をより明確にすることができます。
- switchケースの
default
は、他のcase
ステートメントのいずれも一致しない場合に分岐します。 - 関数名は小文字で始まります。 Goでは、パブリック関数は大文字で始まり、プライベート関数は小文字で始まります。アルゴリズムの内部を世界に公開したくないので、この関数をプライベートにしました。
Hello, world
からこんなに多くを得ることができると誰が知ってたのでしょう。これで、次のことをある程度理解できたはずです。
- テストを書く
- 引数と戻り値の型を使用した関数の宣言
if
、const
およびswitch
- 変数と定数の宣言
失敗するテストを作成してそれを確認する
要件に対応するrelevant
テストを作成し、失敗の説明を簡単に理解できることを確認しました。- 機能するソフトウェアがあることを確認するために、最小限のコードを記述して合格させる。
- リファクタリング、テストの安全性に裏打ちされており、操作が簡単な巧妙に作成されたコードがあることを確認します
今回のケースでは、
Hello()
から Hello("name")
から Hello("name", "French")
に、小さくて簡単な手順で進みました。もちろん、これは「現実の世界」のソフトウェアに比べれば取るに足らないことですが、原則は変わりません。 TDDは、開発するための練習が必要なスキルですが、問題をより小さなコンポーネントに分解してテストできるため、ソフトウェアの作成がはるかに簡単になります。
最終更新 1yr ago