
2025.02.12
職員一人あたり52時間の残業削減に成功 kintone導入がもたらした富士吉田市の自治体DX“変革”ハウツー
リンクをコピー
記事をブックマーク
中村圭助氏:最近、コードを書いていて、mockがしんどいというところがあります。僕は、mockを使う時は、けっこうgomockを使います。(スライドを示して)ここにも書いているんですけど、gomockを使う場合、EXPECT().Return()の型補完が利かないというデメリットがあります。これはなぜかというと、gomock側で自動生成されるコードの受け取る型がanyで定義されているからです。
もう1つ、interfaceのmockの実装がけっこうしんどくなってきます。関数の中で呼び出しているinterfaceのメソッドが増えると、mockが不足したり過剰に実装してしまうことがあります。
gomockを使っている人はこれがわかると思うんですけど、mockが不足していた場合とか、過剰に実装してしまっている場合は、エラーで返されるので、また実装を見直す工程が必要になります。これは開発体験がけっこう悪いかなと思っています。
先ほどもちょっと述べたんですけど、EXPECT().Return()の課題点です。これ、go.devパッケージ(pkg.go.dev)を見ると、Callの部分でinterfaceですね、anyで返すようになっています。
これは、interfaceのスライスになっているので、IDE等の補完が利かない。任意の値を設定できるため、テストの実行時に、単体テストのランタイムでしか型の正しさを検証できないようになっています。
先ほどのEXPECT().Return()の部分で、元の定義した型と異なる型を返すようにReturnする値を実装した場合は、ちょっと拡大すると、(スライドを示して)このように本来はerrorで返すところをtrueで返すように実装しています。
こういう実装をするとanyで渡せちゃうんですけど、実際の実行時にこういうふうに「ここのReturnのargumentの部分がおかしい」「errorなのにboolを返しているよ」と返されます。
そもそも、なぜgomockのEXPECT().Return()の実装がこのような設計になっているのかを考えてみると、mockのフレームワークは、柔軟性がけっこう求められる側面が強いです。
any型であるため、柔軟に値を設定できます。例えば、この中に関数を埋め込んで必要なデータ型を返すという実装ができますが、特定のシーケンスに含まれる場合に柔軟に実装できるようにするために、(スライドを示して)こういう実装になっています。
ただ、先ほど言ったように、IDEの補完が利かないとか、特定のシーケンスを単体テストで実現するというのは、そもそも単体テスト対象の関数が複雑になっていて、any型を渡す場合、内部で型アサーションが起こっているので、潜在的に制御結合の可能性があります。
先ほどboolとエラーの部分で説明したとおり、実際に異なるデータ型を渡している場合、ランタイムでエラーになります。
gomockの柔軟性を落とさない、かつ、IDEの恩恵を受けられるツールがあればよさそうだなっていうのをモチベーションに、機能拡張を今行っています。
柔軟性を落とさないというところで言うと、anyの部分はanyのままで扱い、自動生成されるコードに対しては変更を加えないことをもとに機能拡張しています。
また、その部分で、それは残しつつもIDEの恩恵を受けられるようにするために、EXPECT().Return()に渡す値が、定義したinterfaceと一致する型をスケルトンコードとして生成するということをやっていきます。
ここで一致する型を定義することによって、IDEがその型を読み込むことができるため、中のフィールドにどういうものをセットすればいいのかとか、そういうところでIDEの恩恵をけっこう受けられるかなと思っています。
スケルトンコードのイメージですけど、gomockは、(スライドを示して)このようなかたちです。受け取るEXPECT()のところは、model.Userを受け取って、Returnは、errorのなにも入っていない空文字列を返すかたちで生成できればと思っています。
gomockのmockの部分まで含んだスケルトンコードの自動生成までに行われているところですが、(スライドを示して)これは途中までの実装です。
まず、自動生成されたmockのファイルから依存元の情報を取得する。これはコメントアウトされているので、コメントの部分を解析するだけでできます。import文からinterfaceとmockの依存関係の紐付けを行います。
その中で、関数の中で呼び出されている関数のCallExprと、生成されたmockのファイルを紐付けをして、その後ASTを設定、作成するという工程に入ります。そのASTをテストファイルとして吐き出すことで、gomockのIDEの補完が利くテストコードの自動生成を行おうと思っています。
ここからパッケージの依存関係とか、Goのファイルの依存関係についてです。自動生成されたmockのファイルから依存元の情報を取得するために、どういうふうにするんだというところですが、(スライドの)下のようなコードで、けっこう楽に取得できます。
ここから、interfaceの依存の部分について話していきます。interfaceに依存しているパッケージやGoのファイルは、依存元のパッケージに依存しています。
gomockを使うと、ここのパッケージとかファイルをもとにmockパッケージとしてコードが生成されます。
単体テスト時にこのmockパッケージを参照して、レコードを実装します。そして単体テスト時に、このinterfaceに依存している関数であったりメソッドを呼び出します。
今回機能拡張しているgo-test-generatorのmockの自動生成をどういうふうにやっているかというと、まずmockパッケージを見にいって、mockの情報、依存元の情報であったりmockのファイルの置き場を取得しています。
次に、interfaceに依存しているパッケージ、Goファイルのパッケージ、関数、interfaceなど、すべて取得しています。
その次に、依存元のパッケージ、関数、interfaceをすべて取得していって、その後、依存関係の紐付けとか、mockのASTを取得する、作成することをします。
ここの依存関係の紐付けが、ロジックとしてけっこう複雑なので、今現状は、そこを実装しているかたちです。
ASTが作成されたら、単体テストのファイルとして出力するだけなので、ASTを出力します。
各メソッドとか関数のASTを解析して、呼び出されるCallExprを取得するところですが、だいたいCallExprは、ast.ExprStmtとか、AssignStmtから取得できるので、(スライドを示して)こういうふうにASTが表現されます。ここのCallExprを取っていくかたちになります。
CallExprから関数とかメソッドの定義元を取得するんですけど、ソースコード全体をASTにパースする必要があったり、パッケージ単位でGoのコードを解析したりしないといけないので、go/astだけだとパッケージをまたいだパースはできません。
このgo/astだけで実装すると、実装がけっこう複雑になりますし、独自実装になります。それを楽にするツールが開発されていて、golang.org/x/tools/go/packagesというものを使っています。これを使うと、パッケージごとのソースコード、ASTへのパースとか型情報の取得を楽に行ってくれます。
今の開発のつらみですけど、先ほど最初に挙げた必要なテスト数を列挙するツールは、go/astのみの実装になっているので、golang.orgが提供しているpackagesのツールと互換がない部分がけっこうあるため、つらいです。
例えばで言うと、go/astのみでパースしたfuncのdeclarationと、go/packagesでパースしたfuncのdeclarationを比較する時に、そのまま「==」じゃできないとか、トークンのポジションでわざわざ比較しないといけないというつらみがあります。
「CallExprからレシーバの型情報を取得できる」についてです。実際のコードですけど、interfaceでの型アサーションができるので、これがinterface経由で実装されているのか、呼び出しなのかというのが判定可能になります。
「mockのコードを自動生成する」です。コードの自動生成に必要なものは、mock対象の構造体とか、mockのinterfaceのメソッドに対応する関数の引数、戻り値の型です。最終的に出力したいものは、mockの構造体を初期化する関数です。
(スライドを示して)これは、実装している部分のイメージですが、テストの構造体にmockの初期化関数の型を定義すると、こういうふうな実装になります。先ほど言ったとおり、宣言的に書くようになっています。
現在開発中ですけど、「テストコードのASTを構築する」ところは、(スライドを示して)こういうふうになっています。テストコードの部分の関数の呼び出しとか、スケルトンコードの部分の実装ですけど、ここのbuildSkeltonTestCodeという中で、先ほどのmockの生成も行おうとしています。
実際に自動生成したコードをファイルに吐き出すというところで言うと、(スライドを示して)こういうふうに、os.Statでファイルの存在も確認して、format.Nodeで書き込むことができます。
ここからまとめです。テストカバレッジを満たすために必要なテストケースを自動生成するツールを作りました。必要なテストケースは、基本的には分岐を考えればよいです。ただ、Exportedな関数から呼ばれているUnexportedな関数の分岐はまだ考慮できていないので、今後このあたりも作っていかないといけないと思っています。
gomockで補完がけっこう利かない場所を自動生成して楽にコーディングできるようなツールを作ろうとしています。
先ほども言ったんですけど、パッケージ間依存をgo/astのみで解決するのは、けっこう大変です。packagesという便利ツールがあるので、これを使いましょう。
これも同じですね。Unexportedな関数のmockにも対応できるようにするかたちになっています。
僕の発表は以上で終わります。ご清聴ありがとうございました。
関連タグ:
2025.02.13
“最近の新人は報連相をしない”という、管理職の他責思考 部下に対する「NG指示」から見る、認識のズレを防ぐコツ
2025.02.06
すかいらーく創業者が、社長を辞めて75歳で再起業したわけ “あえて長居させるコーヒー店”の経営に込めるこだわり
2025.02.13
AIを使いこなせない人が直面する本当の課題 元マッキンゼー・赤羽雄二氏が“英語の情報”を追い続ける理由
2025.02.12
マネージャーは「プレイング3割」が適切 チームの業績を上げるためのマネジメントと業務の比率
2025.02.12
何度言っても変わらない人への指示のポイント 相手が主体的に動き出す“お願い”の仕方
2025.02.13
「みんなで決めたから」を言い訳にして仲良しクラブで終わる組織 インパクトも多様性も両立させるソース原理
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.02.10
32歳で「すかいらーく」を創業、75歳で「高倉町珈琲」で再起業 「失敗したからすかいらーくができた」横川竟氏流の経営哲学
2025.02.14
報連相ができない部下に対するコミュニケーションの取り方 「部下が悪い」で終わらせない、管理職のスキル向上のポイント
2025.02.10
A4用紙を持ち歩いて殴り書きでアウトプット コクヨのワークスタイルコンサルタントが語る、2種類のメモ術
着想から2か月でローンチ!爆速で新規事業を立ち上げる方法
2025.01.21 - 2025.01.21
新人の報連相スキルはマネージメントで引きあげろ!~管理職の「他責思考」を排除~
2025.01.29 - 2025.01.29
【手放すTALK LIVE#45】人と組織のポテンシャルが継承されるソース原理 ~人と組織のポテンシャルが花開く「ソース原理」とは~
2024.12.09 - 2024.12.09
『これで採用はうまくいく』著者が語る、今こそ採用担当に届けたい「口説く」力のすべて
2024.11.29 - 2024.11.29
【著者来館】『成果を上げるプレイングマネジャーは「これ」をやらない』出版記念イベント!
2025.01.10 - 2025.01.10