焼売飯店

GoとかTS、JSとか

Goのtesting.TにContext()メソッドが追加されそうという話

この記事はsyumai Advent Calendar 2024の3日目の記事です。

火曜なので、本日は「Go」がテーマとなります。

(*testing.T).Context()とは

(*testing.T).Context() は、以下のProposalでAcceptされた、Goのテストで使用できる新しいメソッドです。

github.com

このメソッドから返却されるContextは、t.Cleanup() によって登録されたクリーンアップ関数が呼び出される直前にキャンセルされます。

Proposalの最終的なAPIは以下の通りです。

// Context returns a context that's cancelled just before
// [T.Cleanup]-registered functions are called.
//
// Cleanup functions can wait for any resources
// that shut down on Context.Done before the test completes.
func (t *T) Context() context.Context

実装は既にmaster branchにマージされていて、現在Go 1.24のマイルストーンでのリリースが見込まれています。 testing.Tへの追加と言いましたが、実際には、testing.T / B / Fで共通のインタフェースであるtesting.TBへ追加が行われたようです。

使い方

ProposalのDescriptionに記載されているのは、次のような使い方です。

func TestFoo(t *testing.T) {
    // 1. Contextを取得
    ctx := t.Context()
    // 2. WaitGroupを作成
    var wg sync.WaitGroup
    // 3. テストの完了時のクリーンアップ処理でwg.Waitを呼ぶように指定
    t.Cleanup(wg.Wait)
    // 4. WaitGroupにテスト内で使用する処理を登録
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 5. テストのクリーンアップ処理の直前まで待つContextを渡す
        doSomething(ctx)
    }()
}

このコードを読むだけではあまりイメージしにくいかもしれませんが、本機能のメリットは「テストの完了まで待ってくれるContextが入手できる」点にあります。

上記の doSomething 関数をもっと具体化してみましょう。

続きを読む

Goで使える郵便番号検索ライブラリ「go-jpostcode」の紹介

この記事はsyumai Advent Calendar 2024の2日目の記事です。

月曜なので、本日は「これまで作ったツール・ライブラリ紹介」がテーマとなります。

本日のライブラリ

今回は、go-jpostcodeを紹介します。

github.com

go-jpostcodeとは?

go-jpostcodeは、郵便番号から住所を逆引きするのに使えるGoのライブラリです。

使い方

使い方は非常に単純で、 github.com/syumai/go-jpostcode をimportして、下記のように関数呼び出しを行ったらOKです。

// 住所を1件検索する
address, _ := jpostcode.Find("0010928")

// 住所を複数件検索する (郵便番号には、複数の住所が紐付くことがあるので)
addresses, _ := jpostcode.Search("1138654")

// JSONとして出力する
addressJSON, _ := address.ToJSON()
fmt.Println(addressJSON)

応用例

例えば、このライブラリを使って郵便番号検索APIを簡単に立ち上げることができます。

続きを読む

syumai Advent Calendar 2024のお知らせ

始まってしまった、12月が…。

ということで、syumai Advent Calendar 2024の開始をお知らせします。

adventar.org

これは何?

syumaiが25日間記事を出来る範囲で投稿し続けるAdvent Calendarです。

どうしてこうなった?

petamorikenさんの書く一人Advent Calendarが読みたい気持ちがありすぎたので、「morikenさんが一人Advent Calendarを書くなら自分も書く」と過去に勝手に発言していて、(全然morikenさん側のきっかけにはなってないと思いますが、)とても嬉しいことにmorikenさんのAdvent Calendarが開始したのを目撃したので、やるか…!となってます

方針

最近の持ちネタ量的に、1つのネタで25記事も書くのは厳しそうというのと、忙しさの波があるというのもあるので、なるべく負担にならない形でやっていこうと思います。

続きを読む

ライブラリとして公開したGoのinterfaceを変更するのは難しいと言う話

昨日Twitterに書いた内容に、sivchariさんとhajimehoshiさんからリプライをいただいたので、備忘録的にまとめておきます。

  • 発端
  • interface型と非interface型の後方互換性について
    • interface型を公開した場合
      • 1. メソッドの追加
      • 2. メソッドのシグニチャの変更
      • 3. メソッドの削除
      • 公開されたinterface型を変更する場合の後方互換性についてのまとめ
    • 非interface型を公開した場合
      • 1. メソッドの追加
      • 2. メソッドのシグニチャの変更
      • 3. メソッドの削除
      • 公開された非interface型を変更する場合の後方互換性についてのまとめ
  • その他の後方互換性を崩さない機能拡張のパターンの紹介
    • 他から実装できないinterfaceにする
    • interfaceを合成する
    • 内部用のinterfaceを分け、構造体型を公開する
  • まとめ

発端

昨日、フューチャー技術ブログに掲載された、こちらのManoさんの記事を読みました。

future-architect.github.io

内容としてはGo 1.20で追加される見込みの context.WithCancelCausecontext.Cause に関するもので、気になったのは context.Cause 関数についての次の記述でした。

Cause()ですが、以下のように context.Context のインターフェースにCause()といった関数を追加してくれた方が利用者としては便利じゃないかと思いますよね。これはGo1互換性ポリシーに書いてあるように、パッケージエクスポートされたインターフェースに新しい関数を追加することは許可されてないということで否定されていました(そのため、context.Contextを引数にとる現在のかたちで提供されています)。

記事中で紹介されているコード例も引用させていただきます。

// 互換性をぶっ壊すAPIイメージ
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
+   Cause() error // ★後方互換性を壊すためインターフェースに新規関数の追加はできない
}

これはとても共感できる内容で、ここで自分が持った疑問は "なぜcontext.Contextは構造体型ではなくinterface型で定義されているのか?" と言うものでした。

それでは、 context.Context のような公開された型に、後方互換性を破壊することなく Cause() メソッドのような公開メソッドを追加する方法について考えてみましょう。

続きを読む

GitHub Sponsorsで支援している方を勝手に紹介する記事【2022年度版】

本記事では、2022年末時点でGitHub Sponsorsで支援している方を勝手に紹介していこうと思います!

現在支援させていただいているのはこちらの方々です。

以下、順番に紹介させていただきます。

続きを読む

Go Quiz Advent Calendar【17日目】 - メソッド値は難しかった編

こちらは Goクイズ Advent Calendar 2020 - Qiita の17日目の記事です。


問題

今回はMethod values (メソッド値) の問題です。(前回のGo Language Specification輪読会で見付けたものです)

package main

import "fmt"

type I interface {
    M() int
}

type T struct {
    a int
}

func (t T) M() int {
    return t.a
}

func main() {
    var t *T = &T{a: 1}
    f1 := t.M

    var i I
    i = t
    f2 := i.M

    t.a = 2

    fmt.Println(f1(), f2())
}

f1(), f2() の結果を問う、引っ掛け無しの問題となります。 さて、答えはどれでしょう?

  1. 1 1
  2. 1 2
  3. 2 2
続きを読む