このコードラボでは、素数を判定するゲームを題材に、Goでコマンドラインツールをつくり、そのテストを行う体験ができます。
コードラボの流れは次のとおりです。
お使いのコンピューター上で Go の開発環境を用意していない方は、Google Cloud Shell というクラウド上の開発環境を利用することで、すぐに Go の開発をはじめることができます。
Google Cloud Shell の詳しい説明と使い方は Google Cloud Shell で Go の開発をはじめよう を参照してください。
GitHub からコードラボで利用するサンプルコードを取得しましょう。
$ git clone https://github.com/WomenWhoGoTokyo/codelab.git
Google Cloud Shell をご利用の方は、https://github.com/WomenWhoGoTokyo/codelab/tree/master/play-with-number の「OPEN IN GOOGLE CLOUD SHELL」ボタンを押下してコードを取得してください。
コードが取得できたら codelab/play-with-number
というディレクトリがつくられていることを確認しましょう。
$ cd codelab/play-with-number $ ls README.md go.mod main.go prime
ファイルの構成は以下の通りです。
取得したサンプルコードを動かしてみましょう。
このサンプルコードは、任意の数字が素数かどうかを判定し、その結果を出力するコマンドラインで遊ぶゲームです。
素数とは、1とその数自身でしか割り切れない自然数のことです。
codelab/play-with-number
ディレクトリで、以下のコマンドを実行します。
$ go build -v -o play-with-number
go build
を実行すると、バイナリファイルが生成されます。
このゲームには二つのモードがあります。
一つ目は todayis モード、二つ目は prime モードです。todayisモードは、本日の日付を6桁の数字にしたものが素数かどうかを判定します。prime モードは、入力した任意の数字が素数かどうかを判定します。
まず todayis モードから試してみましょう。
実行オプションで -game
を指定し、引数に todayis
を入れて、バイナリファイルを実行してみましょう。
$ ./play-with-number -game todayis 20210418は素数ではありません
本日の日付(2021年4月18日)をyymmdd形式で表した6桁の数字は、素数ではないという結果が出力されました。
今度は prime モードを試してみましょう。
先ほどと同じように実行オプションで -game
を指定し、引数に prime
を入れて、バイナリファイルを実行してみましょう。
$ ./play-with-number -game prime
実行するとコンソールに 「数字を入力してください」と表示されます。
好きな文字を入力してみましょう。まず試しに、素数ではない 6 を入力してみます。
$ ./play-with-number -game prime 数字を入力してください 6 は素数です
入力した数字 6 は素数であるという、期待と異なる結果が出力されました。
次の章からは、期待した結果が返ってくるようにプログラムを修正していきます。
次に、テストを実行して、期待と結果が異なる部分を確認します。
prime/
配下の prime_test.go
というファイルを確認します。
codelab/play-with-number/prime/prime_test.go
package prime
import "testing"
func TestIsPrime(t *testing.T) {
tests := []struct {
arg int
want bool
}{
{
arg: 5,
want: true,
},
{
arg: 6,
want: false,
},
}
for _, tt := range tests {
if got := IsPrime(tt.arg); got != tt.want {
t.Errorf("IIsPrime(%v) = %v, want %v", got, tt.want)
}
}
}
これは、prime/prime.go
に書かれている IsPrime という関数をテストするためのプログラムです。入力した数値が素数である 5 の場合は true を、素数ではない 6 の場合は false を返す処理が書かれています。
テストを実行してみましょう。
$ cd prime $ go test
テストの実行結果が出力されました。
--- FAIL: TestIsPrime (0.00s) prime_test.go:22: IsPrime(6) = true, want false FAIL exit status 1 FAIL github.com/WomenWhoGoTokyo/codelab/play-with-number/prime 0.452s
入力値が 5 の場合も 6 の場合も false が返ってきており、期待と異なる結果になっています。このテストがパスするように、入力した値が素数の場合は true が返るようにコードを修正していきます。
まず、codelab/play-with-number ディレクトリにあるmain.goファイルを確認しましょう。
PrimeNumDeterminer
関数では、IsPrime
関数の戻り値が true だった場合に「素数です」と出力し、戻り値が true でなかった場合「素数ではありません」と出力する処理をしています。
codelab/play-with-number/main.go
...(略)
func PrimeNumDeterminer(num int) {
result := prime.IsPrime(num)
switch result {
case true:
fmt.Printf("%dは%s\n", num, "素数です")
default:
fmt.Printf("%dは%s\n", num, "素数ではありません")
}
}
...(略)
また、コンソールに入力した値を読み取る処理で、flag
パッケージを使っています。
codelab/play-with-number/main.go
package main
import (
"flag"
"fmt"
"strconv"
"time"
"github.com/WomenWhoGoTokyo/codelab/play-with-number/prime"
)
var game string
const (
defaultGame = ""
usage = "ゲームのメニューを選択"
)
func init() {
flag.StringVar(&game, "game", defaultGame, usage)
}
func main() {
var (
num int
err error
)
flag.Parse()
switch game {
case "prime":
fmt.Print("数字を入力してください\n")
fmt.Scan(&num)
case "todayis":
num, err = convertTodayToNum()
if err != nil {
fmt.Println(err)
return
}
default:
fmt.Print("オプションを指定してください")
return
}
PrimeNumDeterminer(num)
}
...(略)
flag.String()
やflag.Int()
などの関数を使って、パースしたいパラメータを指定することができます。今回はflag.StringVar()
を使っています。
パースしたいパラメータを指定したら、flag.Parse()
を使ってコマンドラインを解析します。flag.Parse()
は、入力された値を定義されたフラグに変換します。
まず、 prime.go
のIsPrime
関数を確認します。prime とは日本語で素数のことです。
IsPrime
関数では、コマンドラインで引数として与えられた文字列を n とし、nが素数であるか否かの判定を行います。nが素数かどうかは、「1より大きい整数で、平方根をとってそれ以下の素数で割り切れる場合」に素数であると判定できます。
codelab/play-with-number/prime/prime.go
package prime
import (
"math"
)
func IsPrime(n int) bool {
squareroot := math.Sqrt(float64(n))
result := true
//TODO: コマンドラインから取得した値が1の場合の条件分岐処理を書く
for i := 2; i <= int(squareroot); i++ {
if n%i == 0 {
//TODO: 結果の判定をする
break
}
}
return result
}
IsPrime
関数で行っている処理を上から見ていきましょう。
まず、コマンドラインで文字列(string型)として与えられたn
を float64
型に型変換しています。さらに、mathパッケージの Sqrt
関数を使い、その平方根を取得してsquareroot
という変数に代入しています。square root とは日本語で平方根のことです。
続くループ処理で、n
が 2 から自分自身までの自然数で割り切れた場合は結果を true
とし、割り切れずに余りが出た場合は結果を false
とすることで、素数かどうかの判定ができるようになります。
この処理を書くと、プログラムは次のようになります。
codelab/play-with-number/prime/prime.go
package prime
import (
"math"
)
func IsPrime(n int) bool {
squareroot := math.Sqrt(float64(n))
result := true
//TODO: コマンドラインから取得した値が1の場合の条件分岐処理を書く
for i := 2; i <= int(squareroot); i++ {
if n%i == 0 {
result = false
break
}
}
return result
}
更新したプログラムを保存したら、再度バイナリをビルドして、もう一度 prime モードで素数判定を実行してみましょう。
$ go build -v -o play-with-number
$ ./play-with-number -game prime 数字を入力してください 6 6は素数はありません
期待通り、6 は素数ではないと判定されました。
書き直した Go のプログラムが期待通り動作するか確認するために、もう一度テストを実行してみましょう。
$ pwd /Users/{ユーザー名}/dev/codelab/play-with-number/prime $ go test
テストの実行結果が出力されました。
PASS ok github.com/WomenWhoGoTokyo/codelab/play-with-number/prime 0.228s
期待通りにテストがパスしました。
本当に素数が判定できているかどうか確認するために、テストケースを増やしてテストを実行してみます。1 から 13 までの整数をテストケースに追加します。
codelab/play-with-number/prime/prime_test.go
package prime
import "testing"
func TestPrime(t *testing.T) {
tests := []struct {
arg int
want bool
}{
{
arg: 1,
want: false,
},
{
arg: 2,
want: true,
},
{
arg: 3,
want: true,
},
{
arg: 4,
want: false,
},
{
arg: 5,
want: true,
},
{
arg: 6,
want: false,
},
{
arg: 7,
want: true,
},
{
arg: 8,
want: false,
},
{
arg: 9,
want: false,
},
{
arg: 10,
want: false,
},
{
arg: 11,
want: true,
},
{
arg: 12,
want: false,
},
{
arg: 13,
want: true,
},
}
for _, tt := range tests {
if got := IsPrime(tt.arg); got != tt.want {
t.Errorf("Prime() = %v, want %v", got, tt.want)
}
}
}
$ pwd /Users/{ユーザー名}/dev/codelab/play-with-number/prime $ go test
--- FAIL: TestIsPrime (0.00s) prime_test.go:66: IsPrime(1) = true, want false FAIL exit status 1 FAIL github.com/WomenWhoGoTokyo/codelab/play-with-number/prime 0.322s
1の場合も IsPrime
関数の戻り値が true となっており、テストが落ちています。
1は素数ではないので、判定処理のプログラムを修正しましょう。
もう一度、prime.go
のIsPrime
関数を確認します。
codelab/play-with-number/prime/prime.go
package prime
import (
"math"
)
func IsPrime(n int) bool {
squareroot := math.Sqrt(float64(n))
result := true
//TODO: コマンドラインから取得した値が1の場合の条件分岐処理を書く
for i := 2; i <= int(squareroot); i++ {
if n%i == 0 {
result = false
break
}
}
return result
}
ループ処理に入る前に条件分岐を追加し、入力された値が 1 だった場合に false を返す処理を追加しましょう。
入力された値が1の場合は、早期リターンをして false を返し、IsPrime
関数を抜けるようにします。
codelab/play-with-number/prime/prime.go
package prime
import (
"math"
)
func IsPrime(n int) bool {
squareroot := math.Sqrt(float64(n))
result := true
//TODO: コマンドラインから取得した値が1の場合の処理を書く
for i := 2; i <= int(squareroot); i++ {
if n%i == 0 {
result = false
break
}
}
return result
}
修正後のコードは下記のようになります。
codelab/play-with-number/prime/prime.go
package prime
import (
"math"
)
func IsPrime(n int) bool {
squareroot := math.Sqrt(float64(n))
result := true
if n == 1 {
return false
}
for i := 2; i <= int(squareroot); i++ {
if n%i == 0 {
result = false
break
}
}
return result
}
修正したコードを保存したら、もう一度テストを実行してみましょう。
$ pwd /Users/{ユーザー名}/dev/codelab/play-with-number/prime $ go test
PASS ok github.com/WomenWhoGoTokyo/codelab/play-with-number/prime 0.344s
期待通り、全てのテストがパスしました。
ここまでの修正を経てプログラムが期待通りに動くようになったので、もう一度バイナリをビルドして、prime モードで素数判定を実行してみましょう。
$ go build -v -o play-with-number
$ ./play-with-number -game prime 数字を入力してください 13 13は素数です
このコードラボでは、素数を判定するゲームを題材に、Goでコマンドラインツールをつくりそのテストを実行する体験をしました。
Goでは、標準パッケージを使って簡単にコマンドラインツールをつくることができるので、みなさんも趣味や業務に役立つツールをつくってみてください。