このコードラボでは、素数を判定するゲームを題材に、Goでコマンドラインツールをつくり、そのテストを行う体験ができます。

コードラボの流れは次のとおりです。

  1. Go で書かれたサンプルコードを取得・確認する。
  2. サンプルコードを動かしてみる。
  3. サンプルコードのテストを実行する。
  4. サンプルコードを修正する。
  5. サンプルコードを実行して、期待する結果になるか確認する。
  6. サンプルコードのテストを修正する。
  7. サンプルコードのテストを実行して、期待する結果になるか確認する。

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型)として与えられたnfloat64 型に型変換しています。さらに、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では、標準パッケージを使って簡単にコマンドラインツールをつくることができるので、みなさんも趣味や業務に役立つツールをつくってみてください。