Go のビルドでは、overlay オプションを使うことで特定のファイルを別のファイルで置き換えてビルドすることができます。

このワークショップでは、overlay の基本的な使い方とテストでの実践的な利用方法について学びます。

まずは、簡単な Go プログラムを題材に overlay を使ってみましょう。

下記のコマンドでこのリポジトリに含まれるプログラムを実行してみてください。

> go run ./overlay/example

このプログラムは、現在時間を time.Now() で取得して、それを表示するだけのシンプルなプログラムです。

package main

import "time"

func main() {
	n := now()

	println(n.String())
}

func now() time.Time {
	return time.Now()
}

表示される時間はプログラムを実行した現在の時間となるので、毎回異なる値が表示されます。

この現在時刻について、「プログラムの実装に変更を入れることなく実験などのために現在時刻を固定したい」ということを想定してみましょう。

このような場合に、Go のビルドの overlay 機能を使うことで標準パッケージの time.Now を置き換えて固定の時刻を返すようにすることができます。

まずは、overlay のための設定ファイル(JSON)を見てみましょう。

> cat ./overlay/example/overlay.json
{
  "Replace":{
    "${GOROOT}/src/time/time.go": "./overlay/example/time/time.go"
  }
}

${GOROOT} は Go のインストールディレクトリ(go env GOROOT コマンドで取得できる値)に置き換えてください。

この JSON ファイルは、Replace というキーを持つオブジェクトを定義しています。このオブジェクトの中で、time/time.go という標準パッケージのファイルを、このリポジトリに含まれる ./overlay/example/time/time.go という別のファイルで置き換えることを指定しています。

この設定ファイルを使って overlay を利用してみましょう。下記のコマンドを実行してみてください。

> go run -overlay ./overlay/example/overlay.json ./overlay/example
2025-09-27 00:00:00 +0000 UTC

このコマンドでは先程と同じプログラムを実行していますが、表示される時間が 2025-09-27 00:00:00 +0000 UTC に固定されていることがわかります。異なる部分は、-overlay オプションで overlay.json ファイルを指定していることです。

go buildgo test では、-overlay というオプションを提供しています。このオプションは、指定した JSON ファイルの内容に基づいてビルド時に特定のファイルを別のファイルで置き換えるためのオプションです。

このリポジトリに含まれる ./overlay/example/time/time.go ファイルは、標準パッケージに含まれる time/time.go ファイルをコピーして、下記のように Now 関数を固定の時刻を返すように書き換えたものです。

// ...

func Now() Time {
	return Date(2025, 9, 27, 0, 0, 0, 0, UTC)
}

// ...

-overlay オプションを使うことで、この書き換えた Now 関数がビルド時に利用されるようになり、プログラムの実行時に time.Now() を呼び出すと固定の時刻が返されるようになります。

このように、Go のビルドが提供している overlay 機能を使うことで特定のファイルを別のファイルで置き換えてビルドすることができるので、プログラムの実装に変更を加えることなく動作を変更することができます。

先程の例では、go run コマンドで main 関数からはじまるプログラムの実行に overlay を利用してソースコードの一部を置き換えました。

より実践的な利用例として、テストコードで overlay を利用する方法を見てみましょう。下記のテストコマンドを実行してみてください。

> go test ./overlay/example

--- FAIL: TestNow (0.00s)
...

このテストは先程見ていた now 関数のためのテストで、下記のように実装されています。

package main

import (
	"testing"
	"time"
)

func TestNow(t *testing.T) {
	t.Parallel()

	want := time.Date(2025, 9, 27, 0, 0, 0, 0, time.UTC)
	got := now()

	if !got.Equal(want) {
		t.Errorf("now() = %v; want %v", got, want)
	}
}

このテストコードは、now 関数が返す現在時刻が 2025-09-27 00:00:00 +0000 UTC であることを期待していますが、通常のビルドでは now 関数は time.Now() を呼び出して現在の時刻を返すため、テストは失敗します。

このテストを overlay を使って実行してみましょう。下記のコマンドを実行してみてください。

> go test -overlay ./overlay/example/overlay.json ./overlay/example
ok      github.com/newmo-oss/gocon25-workshop/overlay/example   0.001s

この go test コマンドでは、先程のステップと同じように -overlay オプションを指定しているため、time.Now() の実装が置き換えられて固定の時刻を返すようになり、テストが成功していることがわかります。

このように、テストにおいて overlay を利用することで実装を変更することなく特定の関数の動作を置き換えてテストを実行することができるようになり、場合によっては通常のビルドでは難しいテストを実行できるようになります。

このワークショップでは、Go のビルドが提供している overlay 機能の基本的な使い方と、テストでの実践的な利用方法について学びました。

日常的な開発において overlay 機能を多用することはあまりないかもしれませんが、特定の関数の動作を置き換えたい場合などに役立つことがあるので一つの解決策として覚えておくと良いでしょう。