ksg

ksg@software_engineer

Portfolio Blog

Go2026/07/21

【中学生でもわかる】Go言語入門⑥エラー処理とテストの書き方(エラーハンドリング・テスト編)

【中学生でもわかる】Go言語入門⑥エラー処理とテストの書き方(エラーハンドリング・テスト編)

前回のおさらい、そしてこのシリーズもいよいよ最終回

前回は、ゴルーチンとチャネルによる並行処理について学びました。今回はこのシリーズの最終回として、

  • プログラムがうまくいかなかったときの対処法(エラーハンドリング)
  • 書いたプログラムが正しく動くことを確認する方法(テスト)

の2つを学びます。この2つは、実際に人に使ってもらうプログラムを作る上で、欠かせない知識です。

エラーってなに?

プログラムは、いつも思い通りに動くとは限りません。たとえば、

  • 存在しないファイルを開こうとした
  • 数字を0で割ろうとした
  • インターネットに接続できなかった

このように、「実行してみたら、うまくいかなかった」ということは、実際のプログラムでは頻繁に起こります。こうした「うまくいかなかったこと」を表すデータのことを、エラー(error) と呼びます。

エラーが発生したときに、プログラムが何の対処もせずに動き続けてしまうと、おかしな結果を表示したり、最悪の場合は突然プログラムが落ちてしまったりします。そのため、「エラーが起きたときに、どう対処するか」をあらかじめプログラムに書いておくことが、とても重要です。これを**エラーハンドリング(エラー処理)**と呼びます。

Goのエラーハンドリングの考え方

多くのプログラミング言語では、エラーが起きると、プログラムの流れが強制的に中断されて、専用の仕組み(try-catchなど)でそれをキャッチする、という方法がとられます。

一方でGoは、少し違う考え方を採用しています。第3回で学んだ「関数は複数の値を返せる」という特徴を思い出してください。Goでは、「エラーが起きるかもしれない処理」は、本来欲しい結果と一緒に、エラーも返り値として返す という設計が基本になっています。

実際にコードで見てみましょう。「わり算をする関数」を作りますが、0で割ろうとした場合はエラーを返すようにします。

package main

import (
	"errors"
	"fmt"
)

func divide(a int, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("0で割ることはできません")
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 2)
	if err != nil {
		fmt.Println("エラーが発生しました:", err)
		return
	}
	fmt.Println("結果:", result)

	result2, err2 := divide(10, 0)
	if err2 != nil {
		fmt.Println("エラーが発生しました:", err2)
		return
	}
	fmt.Println("結果:", result2)
}

実行結果は次のようになります。

結果: 5
エラーが発生しました: 0で割ることはできません

順番に確認していきましょう。

func divide(a int, b int) (int, error) {

戻り値の型が (int, error) となっています。「わり算の結果(int)」と、「エラー(error)」の、2つの値を返す関数だという意味です。errorはGoに標準で用意されている型で、「エラーの内容を表す値」を扱うための型です。

if b == 0 {
	return 0, errors.New("0で割ることはできません")
}

もしbが0だったら、「わり算の結果としては、とりあえず0を返し、errors.New(...)で作った、新しいエラー(エラーメッセージ付き)を返す」という処理です。errorsパッケージのNew関数は、「文字列からエラーを作る」ための機能です。

return a / b, nil

正常にわり算ができた場合は、「計算結果」と、「エラーはありません」という意味の nil(ニル)を返します。nilは、「何もない、空っぽ」を表す特別な値です。「エラーは発生しませんでした」ということを、nilを返すことで表現しています。

result, err := divide(10, 2)
if err != nil {
	fmt.Println("エラーが発生しました:", err)
	return
}
fmt.Println("結果:", result)

呼び出す側では、必ずresult(結果)とerr(エラー)の両方を受け取ります。そして、if err != nil { ... }という形で、「エラーが発生していないかどうか」を必ず確認する、というのがGoの決まったパターンです。「もしerrnilではなかったら(=何かエラーが起きていたら)、エラー処理をして、そこで処理を終える(return)。そうでなければ、正常な結果を使って処理を続ける」という流れになります。

この「関数を呼んだら、必ずerrを確認する」という書き方は、Goのコードのいたるところで登場します。慣れないうちは「if err != nilばかり出てきて、コードが長く感じる」と思うかもしれませんが、これは「エラーの可能性を、その場その場できちんと確認する」という、Goが大切にしている考え方の表れです。

テストってなに?

続いて、テストについて学びます。

テストとは、ひとことで言うと 「書いたプログラムが、期待通りに動いているかどうかを、自動でチェックする仕組み」 です。

たとえば、先ほどのdivide関数について、「divide(10, 2)を実行したら、本当に5が返ってくるだろうか?」「divide(10, 0)を実行したら、本当にエラーが返ってくるだろうか?」ということを確認したいとします。

これを毎回、fmt.Printlnで表示して、自分の目で結果を確認する、というやり方でもできなくはありません。しかし、プログラムが大きくなって、確認したい項目が何十個、何百個にもなってくると、目視でのチェックはとても大変になり、ミスも起きやすくなります。

そこで、「こういう入力を渡したら、こういう結果が返ってくるはずだ」という確認作業自体を、プログラムとして書いておき、実行するたびに自動でチェックしてもらう、というのがテストの考え方です。学校のテストのように、「正解」があらかじめ決まっていて、それと実際の結果を照らし合わせる、とイメージしてください。

Goには、テストを書くための仕組みが標準で用意されています。追加のインストールなどをしなくても、すぐにテストを書き始められるのも、Goの特徴の1つです。

実際にテストを書いてみよう

先ほどのdivide関数のテストを書いてみましょう。Goのテストには、いくつかのお約束のルールがあります。

  • ファイル名を 対象のファイル名_test.go にする(例: main.go のテストなら main_test.go)
  • テスト用の関数名は Test から始める
  • testing パッケージを使う

まず、main.goを次のようにしておきます(先ほどのdivide関数だけを残し、main関数はシンプルにします)。

package main

import (
	"errors"
	"fmt"
)

func divide(a int, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("0で割ることはできません")
	}
	return a / b, nil
}

func main() {
	fmt.Println("プログラムを実行しました")
}

同じフォルダに、main_test.go というファイルを新しく作り、次のように書きます。

package main

import "testing"

func TestDivide_正常なわり算(t *testing.T) {
	result, err := divide(10, 2)

	if err != nil {
		t.Errorf("エラーが発生しないはずが、発生しました: %v", err)
	}

	if result != 5 {
		t.Errorf("結果は5になるはずですが、%dでした", result)
	}
}

func TestDivide_0で割るとエラーになる(t *testing.T) {
	_, err := divide(10, 0)

	if err == nil {
		t.Errorf("エラーが発生するはずですが、発生しませんでした")
	}
}

1つずつ確認しましょう。

func TestDivide_正常なわり算(t *testing.T) {

テスト用の関数は、名前が必ずTestから始まる必要があります。それ以降の名前は自由につけられるので、「何をテストしているか」がわかる名前をつけるのがおすすめです(日本語を使うこともできます)。引数の t *testing.T は、テストの結果を報告するための道具だと思っておいてください。

result, err := divide(10, 2)

if err != nil {
	t.Errorf("エラーが発生しないはずが、発生しました: %v", err)
}

if result != 5 {
	t.Errorf("結果は5になるはずですが、%dでした", result)
}

divide(10, 2)を実行して、「エラーが発生していないか」「結果が期待通り5になっているか」の、2つを確認しています。t.Errorf(...)は、「もし条件に合わなかったら、このメッセージとともに、テストを失敗として報告してください」という意味です。逆に言うと、t.Errorfが一度も呼ばれなければ、そのテストは「成功」として扱われます。

2つ目のTestDivide_0で割るとエラーになる関数では、divide(10, 0)を実行したときに、「きちんとエラーが返ってくるかどうか」を確認しています。_(アンダースコア)は、「この値は使わないので、受け取ったことにはするけれど、無視します」という意味の特別な書き方です。今回は結果の数値そのものには興味がなく、エラーが発生したかどうかだけを確認したいので、結果のほうを_にしています。

テストを実行してみよう

ターミナルで、main.gomain_test.goがあるフォルダに移動して、次のコマンドを実行します。

go test

すべてのテストが成功すると、次のように表示されます。

PASS
ok      hello-go        0.123s

PASS(パス)は「合格」という意味です。試しに、main.godivide関数の中身を、わざと間違えてみましょう(たとえばreturn a / b, nilreturn a + b, nilに変えるなど)。もう一度go testを実行すると、今度はテストが失敗し、t.Errorfで書いたメッセージとともに、どこがおかしいのかが表示されるはずです。

このように、「プログラムを修正するたびに、go testを1回実行するだけで、これまで書いた確認項目がすべて正しいままかどうかを、瞬時にチェックできる」というのが、テストの大きなメリットです。プログラムが育っていくにつれて、この安心感はどんどん大きくなっていきます。

まとめ、そしてこのシリーズのまとめ

今回は、以下のことを学びました。

  • エラーは「処理がうまくいかなかったこと」を表すデータ
  • Goでは、エラーが起きうる関数は「結果」と「エラー(error型)」の両方を返し、呼び出す側はif err != nilで必ず確認するのが基本パターン
  • テストは「プログラムが期待通りに動くかを、自動でチェックする仕組み」
  • Goでは、〇〇_test.goというファイルに、Testから始まる関数を書き、go testコマンドで実行する

これで、全6回のGo言語入門シリーズは完結です。振り返ってみると、

  1. Goとは何か、環境構築、Hello, World!
  2. 変数、型、if/for/switch
  3. 関数とパッケージ
  4. 構造体とインターフェース
  5. ゴルーチンとチャネル(並行処理)
  6. エラーハンドリングとテスト

という道のりを一緒に歩いてきました。ここまで読んで、実際に自分の手でコードを書いて動かしてみた方は、もうGoプログラミングの基礎はしっかり身についています。

ここから先は、簡単な電卓プログラムを作ってみる、Webサーバーを立ち上げてみる、といった「実際に何かを作ってみる」ステップに進んでいくのがおすすめです。手を動かしながら学んだ知識を使うことで、理解はさらに深まっていきます。

これまでお読みいただき、ありがとうございました。ぜひこれからも、Goでのプログラミングを楽しんでください。

おすすめの記事