FreeStyle リブレで血糖値測定生活を試してみた

2 年前に患った病気 & 手術の影響で、僕は将来的に血糖値に問題を抱える、もっと言えば糖尿病に非常にかかりやすくなるという状態になった。(ここまで書くと、もうどこに疾患があったのか分かる人も居ると思うけど)

これ自体は担当してくれた医師からも、その問題が何年後・何十年後に起こるかは分からないが、気をつけていくしかないという感じで、定期的な検査や健康診断の数値を見ていこうという感じ。

健康診断で HbA1c にシグナル

3 ヶ月に一度は採血での血糖値(だけじゃないけど)の測定もかかりつけの病院でやってもらっていて、術前よりは高いが問題がある様子もなく「体重減りすぎてる方が問題だから食べたら?」と言われるぐらいだった。

ただ、夏の健康診断の HbA1c が要注意ゾーンに入っていた・・ 😩

dmic.ncgm.go.jp

www.ncvc.go.jp

糖尿病予備群ってやつですかね。。
とはいえ、HbA1cは定期的な検査では計測していないし、他の数値と総合して判断することになると思うので、まあまだ気をつけましょうレベルではあると思います。

ただ、自分の場合は、インスリンの分泌量の限界が人よりも早く来ることになるはずなので、この年齢でその兆候が出始めたってことは、結構危機感もって生活しないといけないなと思ったのでした。

いい機会なので自分の状態を知ってみたい

FreeStyle リブレは存在自体は「医者が教える食事術 最強の教科書*1」という本を読んだ時に知って、試してみたいと思っていました。

加えて、術前から「食後に急激に眠くなる」ようなことも頻繁にあったので血糖値スパイクみたいなものも出ているか観察してみたいなどありました。

購入・装着などなど

note.mu

上記の記事が写真も付いていて詳しいです。
記事にもありますが、最初は結構ドギツイ針に緊張しますが痛みはまったく無いですし、抜く時も何もありませんでした。(抜いて分かりましたが、刺さっていたものは柔らかい)

個人的な所感

実は、2 週間の計測期間のうち、1 週間(いや、もっとか。。)ぐらいは体調不良で熱出して寝込んでいたりで実験はあまりできませんでした。その中でも行ったは以下のようなことです。

  • ビール 1 缶 (350ml) 飲んだらどうなる?
  • ワイン飲んだらどうなる?
  • マクドナルドのセット食べたらどうなる?
  • 食後すぐに運動したらどうなる?
  • 食後 30 分 / 1 時間 後に運動したらどうなる?
  • その他食事のバリエーション
    • 家での食事、外食での糖質高め、低めなど

できなかったのは、パスタやグラノーラを食べた時の反応や、早食いするかしないかの違い、お酒の量での違い、果物の反応とかですかね。 低GI食品とかも試してみたかった。 逆に、体調崩してあんまり運動の組み合わせができなかった分、食事からの血糖値の自然な増減はよく見れたかもしれません。
後、お盆時期は実家にいて、親はあんまりそういうの分からないので、平気で糖質高そうなのぶっこんでくるので、まあ実験としては良かったかも。(バナナ食べる?とか)

どんな感じだったか

あくまで自分の身体に対する反応なので、誰でもこうって訳じゃないです。

  • ゆっくり食べるのは血糖値の上昇を緩やかにするのにすごく効果が高い
    • 同じように白米がメニューに入っていても、外食よりも自宅で超ゆっくり食べる方が上がり方低い。最大値も上がる速度も。
    • 超ゆっくりというのは、僕の場合一時間ぐらいかけて全体の食事をする感じ。
  • 液体から糖質取ると反応がめちゃ早い
    • ビールは言わずもがなですが、美容院でちょっと出された微糖の紅茶を 150ml ぐらい飲んだだけで、短時間でめちゃくちゃ上がった。
    • 後、知らずに糖質含まれてる青汁を朝飲んでいたが、まあまあ上がってた。(糖質無しのに変えた。。)
    • 実家で、身体にいいからと甘酒飲まされたけど、その後の上がり方もえぐかった。
  • SOYJOY は僕は上がり方が少なかった
    • 低GIとうたっているだけはあるのかもしれない。外に出てどうしても食べるものが選べない時は案外いいかもしれない。
  • マクドナルドのセットはキツイ
    • ポテトのセットに加えて、サラダも頼んで最初にサラダを食べたが、それでも最大値もそこからの持続時間も相当だった。まあ、じゃがいもにパンだし、どうしてもゆっくり食べるのが難しい。
    • 自宅に持ち帰って、他にも食べるものがあってゆっくり食べるものがあればいいが、外食として使うのは厳しそう。
    • 実家の近くにあるのでマクドナルドを例に出したけど、ここで言いたいのはポテトとハンバーガーみたいなセットの話。
  • 運動は食後すぐが効果がありそうだけど、たまに運動後にも上がるケースがあった
    • ここは諸説あるし、人によったり、食事のスピードにも依存しそう。
    • いずれにしろ、運動後に上がるケースも、最大値は低く抑えられているので効果はありそう。

まあざっとこんな感じ。

今後やっていきたいこと

  • ゆっくり順序よく食べる
    • 揚げ物や少量の白米ぐらいなら、ゆっくり順序よく食べることで血糖値の上昇は僕はだいぶ抑えられることが分かった
  • 糖質の多い飲み物は避ける
    • 噛んで消化するものと違って速度が違う
  • 食後に運動する
    • 特に糖質を取った後。とにかく急激に下がる。
    • 筋肉がつくことでもっと効果が上がる(可能性)

その他

  • 血糖値スパイクはどうだったか
    • 2 度ほど実験してみたが、その傾向が見られた。もちろん自己判断なので確実なことは言えないけど。
  • 結構低血糖状態になってる
    • 高血糖状態よりも低血糖になっている頻度の方が高い。血糖値スパイクの後に、というよりも血糖値意識し過ぎて食事自体を抑えている可能性がある。
    • チーズとかナッツでの間食を増やすようにしてる。
  • 食事
    • 卵と乳製品と鶏肉メインみたいな感じ
    • 昼は夜の残りで夜は食後の運動前提で割と自由に作るので、過度に糖質は気にしてない(と言っても、麺類や白米は食べないし、砂糖は気を使うけど、他に調味料にはそこまで気を使わない)

最後に

別にライザップしたい訳じゃなく、自分の身体のためという点でこういう事を気にすると、在宅リモートで働くというのは、かなり重要な要素になってくる。
通勤で決まった時間に勤務しててもできなくは無いと思うが、「適した食事内容」「ゆっくり食事」「食後の運動」とかを考えると、よくある「1 時間の昼休憩」「外食主体の昼飯」とかになると、どうしても狙ったような生活はできない気がする。

まあ、在宅リモートが全てではないにしろ、こういう人間も働きやすい環境が増えるといいなと思う。

親と話していると「インスリン注射しながら毎日飲み会言ってる営業さんとかいたわよ (笑)」みたいな事を言われるが、個人的にそういう生活の前例作るのはマジでやめて欲しいと思う。。

*1:本の内容は僕のような糖質避ける意味がある人にはいくらか参考になりますが、誰にでもオススメするものではないです。

「NO HARD WORK!」を読んだ

1 月末の一泊二日の出張中に読むことができました。
日常だと在宅勤務と家事全般(週の間に妻と交代しているが、食事を作るか娘を迎えに行って風呂入れたりするかの違いなので自由時間はいずれにしろお互い無い)でゆっくり本を読む時間は週末に実家で娘を放置できる時ぐらいしか無いんですが、飛行機とか乗るとホントに読書が捗ります。(とはいえ、毎日電車乗ってた東京勤務時代にめっちゃ本読んだかと言えばそうでもないので、まあたまの機会だからでしょう 😅)

読んで時間も経ってしまったので早くも忘れて来てしまっていますが、なんとなくの感想記事です。

前もって言っておくと、僕はこの作者コンビの本はこれまでも読んできているし、単純にファンでもあるので悪い感想を書くことは無いです。

全体的に

これまでの本の記憶もあるので、会社をバージョンアップしてきている感じが伝わってきます。
後は、タイトル通りというか原題が「It Doesn't Have to Be Crazy at Work」なので、そういう働き方に対しての意見が多くて、自分の周りを見回してみたり、自分を振り返ってみたときに、悪い意味で頷ける部分が多かったです。まあ、彼ら自身も過去を振り返って「こうした時もあったが・・・」みたいに書いてあるとおりで、会社もフェーズを経て変わっていくものだと思うので「今、自分の所属する組織がどうか」というのはそこまで大事じゃないとは思います。ただ、「どう変わっていけるかの可能性」は見極めないといけないと思いますが(または、ここまでで変わってこれているかとか)。

ただ、「世界を変えるな」はだいぶ笑いました 😇 いろんな会社がそういう「世界〜」的なミッション掲げてますもんね。

個別で

正直だいぶ忘れてきてるんで、個別に記憶に残ってるのがほとんど無い。。

生産性より効率

いくつかの作業をある時間内につめこんだり、できるだけ多くの仕事をできるだけ短い時間にすませたりすることに意味などない。

仕事が速い人は、どんどんタスクが積まれていく問題にも通じそうですけど、生産性で語ることに感じていた違和感をズバッと言われた気がしました。

同意ではなく協力を求めよう

これは、すぐに妻に話したぐらいで一番良かった項目でした。

僕自身、これまで「全員で同意してこそ、同じゴールを目指せる」みたいな気持ちを持っていて、何かを決める時にできるだけ全員の同意を取ろうとしてきました。
ただ、この項目読んで、実際(自分が同意する側であっても)渋々同意していたり、内心は同意していないみたいなケースはやっぱりあったよなぁと思い出しました。

「反対だけど、コミットする」これは明日から使いたいフレーズです。

ベストプラクティスは幻

毎年「Rails は死んだ」的な記事を見ますし、目新しいフレームワークや言語・ツールは日々出てきて、それに対する「これがベスト」的な記事もたくさん出て、僕自身「ほうほう、JAMStack というのがいいのかぁ」とか日々流されています 😣

Rails 自体が本の作者の DHH が作ったものですが、当時は Ruby 自体「ベスト」なものでは無かったはずですし、フロンエンドに対する手法も世間が React だ Vue だと騒いでいる中で pjax のような turbolinks を作ったり、最近だと小さなフロントエンドのフレームワークである stimulasjs を作っています。
iOS/Android に対しても、turbolinks と連携するそれぞれのネイティブの小さな WebView ラッパー(だいぶ乱暴な説明なので詳細は公式ページを 🙏)を用意したという感じです。

たぶん、フロントエンドやネイティブの最前線を追っている人達にとっては面白みのない取り組みに見えるのだろうと思いますが、Basecamp のカルチャーやメンバー、メンバーのスキルセット等からチームとして最も calm に運用できる仕組みを求めた結果なのだろうと想像します。

組織拡大を目標に持ってしまうと、「採用に有利になる、人員を確保しやすい」のような理由で「ベストプラクティス」に登場してきそうな技術選択をする事もありますが、なにか、そういう姿勢を貫く感じを実際の技術選択からも見せてもらえた気がして、この項目はグっときました。

最後に

総じて、日本のメディアで話題になるイケイケのベンチャーみたいな働き方とはだいぶ違うので、誰におすすめとかは無いのですが、働き方を見直したいなと思ってる人はさっと読んでみるといいかもしれないです。まあ、この本自体もベストプラクティスではないので、軽い気持ちで。

久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - MockHttp -

dany1468.hatenablog.com

上記の記事の続きです。

WebMock の想い出

github.com

Ruby の開発の現場にいて、最高感があったことの一つに WebMock がありました。それまでは Web API を利用するようなコードを書く場合は、その Client のコードをまとめて Mock するようなことをする必要があり、個人的にですがやりすぎ感があるなぁと思っていました。

WebMock を使うと主要な HTTP ライブラリを使っている限りはそのリクエストを簡単にモックすることができました。

HttpClient ができていた

久しぶりに C# に戻ってくると HTTP リクエストは HttpClient がほぼスタンダードになっているようでした。今回私が利用した MockHttp はその HttpClient に対しての Mock の振る舞いを提供してくれます。

MockHttp

github.com

上述したように HttpClient を差し替える Mock を簡単に作成できます。

これの基本的なアイデアHttpMessagingHandlerDelegatingHandler を使って HttpClient の Hook を実装している点です。

なので、Web API の Client ではこの辺りを利用したテストが書かれている事がよく見られます。

github.com

github.com

MockHttp は ExpectMatch の記法を提供してくれているため、テストをより可読性が高く、少ない記述で書けるようにしてくれています。

使い方は正直 README 以上のものは私は行っていないので説明はしません。

余談

Ruby では Faraday を使って Web API Client を記述する事が多かったです。Faraday には Middleware という仕組みがあり、それを使って例外処理等を書くことがありました。

Ruby の HTTP Client「Faraday」を使った場合の例外の扱いとリトライ処理をどうするか考えてみた - Qiita

HttpClient でも Faraday のようにリクエスト前後に Hook を挟めることを学べて良かったです。
一方で HttpClientIDisposable を実装しているにも関わらず、 using で囲って使うことが(ケースによっては)アンチパターンであったりと、気をつける点もあり「うーーん」と思ったりもしました。

配列もリストも Array だけで表す Ruby を使っていると、コレクションだけでもかなりの種類がある C# は、もちろん使い分けることが良いのでしょうが面倒に感じてしまうことがあり、もっと簡単にならないかなぁとぼんやり思ったりもしましたが、郷に入っては郷に従えなのでちゃんとやろうと思ったのでした 😅

久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - EF Core で自動生成 -

dany1468.hatenablog.com

上記の記事の続きです。

SQL を書かずにテストコードを書いていきたい

これは環境によると思いますが、私が担当したアプリケーションはメインのプロダクトからは切り離されていて、そちらで定義されているであろう Entity や Repository を利用できないという状態でした。

もちろん上記を是正していくのが本来の筋ではありますが、そんな事がさっと聞き入れられれば苦労はしないというのがよくある話で、こちらも例外ではありませんでした。

かといって、テスト用のデータを作るために何十とあるテーブルの INSERT や UPDATE を書くのは長らく Rails で開発していた人間としては指の筋力が足りません。。それにそれを読むための眼の筋肉も。

Entity Framework Core を使う

Entity Framework を使えば、RailsActiveRecord のように Model class を主にしてデータ投入等ができるので、記述量や読みやすさが向上します。速度が重視される現場であれば、「Dapper でしょ」みたいな圧を受けるかもしれませんが、テストコードであれば許容できると思います。(もちろん、Dapper を使っていても Repository 等で wrap されているので、それを使えればテストデータの作成に困ることはないと思うので、私の環境は特殊だとは思いますが)

www.npgsql.org

私の担当したアプリケーションは Npgsql を利用していたため、上記の記事を見つけました。既存の RDB から Model class を生成できるというものです。(もちろん Npgsql に限った話しではありません。)

私は、この EF Core で作成した Model class のみを保持する project を作成し、それをテスト用の project で参照し、前の記事で触れた Bogus と組み合わせて Factory から生成するようにしています。

Scaffold で自動生成

まずは必要なパッケージを追加して以下のように csproj を整えます。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" />
    <PackageReference Include="Npgsql" Version="4.0.2" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.1.1" />
  </ItemGroup>

</Project>

.NET CLI に Entity Framework 用のコマンドが使えるようになっているので、以下のようにして実行します。
参考: dotnet ef dbcontext スキャフォールディング - Microsoft Docs

% dotnet ef dbcontext scaffold "Host=***;Database=***;Username=***;Password=***;Port=***;" Npgsql.EntityFrameworkCore.PostgreSQL --context 名前空間

これで Model class が生成されます。

Scaffold された Model から migration を行う

本来の使い方とは異なりますが、Scaffold で生成された Model class から migration を作成し、それを使って CI 環境用の RDB 環境を作ることに利用できます。

% dotnet ef migrations add InitialCreate --context MigrationContext

この場合、 scaffold と migration の向き先が異なるため以下のように migration 用に以下のように DbContext を定義して利用しています。

namespace Sample
{
    public class MigrationContext : SampleDataContext // scaffold 時に指定したもの
    {
        public MigrationContext() : base(Environment.GetEnvironmentVariable("MIGRATION_CONNECTION"))
        {
        }
    }
}

この場合は以下のように migration を実行します。

% MIGRATION_CONNECTION="Host=***;Database=***;Username=***;Password=***;Port=****;" dotnet ef migrations add InitialCreate --context MigrationContext

以降は、必要なタイミングで scaffold と migration を実行し、テストを健全に保っていきます。

が、まだ私はこの辺りを完全には自動化できておらず微妙な感じにはなっています。

久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - Bogus -

dany1468.hatenablog.com

上記の記事の続きです。

factory_bot みたいなのが欲しい

Ruby の開発現場ではテストデータの生成に factory_bot を利用していました。 factory_bot の利用自体には賛否あると思いますし、メンテナンスが大変になるケースもありましたが、それでも Rails の Model のデータからさくっと Factory を定義できて、インスタンス化やデータの投入も簡単にできて重宝しました。

5 年以上前に Plant という factory_bot like なパッケージを使っていることをこのブログに書いたことがあります。

github.com

ただ、もう何年もメンテナンスされておらず、メンテナンスしてまで使いたいとも思えず別のものを探すことにしました。

Bogus

github.com

README より引用 Hello. I'm your host Brian Chavez (twitter). Bogus is a simple and sane fake data generator for .NET languages like C#, F# and VB.NET. Bogus is fundamentally a C# port of faker.js and inspired by FluentValidation's syntax sugar.

上記の通り factory_bot というよりは Faker です。

Factory としても結構使える

Bogus には基本は Usage を見てもらうように、ある class に対して RuleFor を設定していくことで、生成するデータのルールを設定していき、 Generate で生成するという形です。

また、 FinishWith というオプションを使うと、 Generate でのインスタンス生成後に実行するルールを追加できます。 factory_bot での after(:build) callback のように使えます。

加えて RuleSet というオプションが用意されていて、これを利用すると複数のルールに名前をつけて登録することができ、個別にそのルールを適用した上で呼び出すことができます。

var testUsers = new Faker<User>()
    .RuleSet("Male", set => set.Rules((f, fa) => 
        {
            fa.FirstName = f.Name.FirstName(Gendar.Male);
            fa.LastName = f.Name.LastName(Gendar.Male);
        }
    ))
    .RuleSet("Female", set => set.Rules((f, fa) => 
        {
            fa.FirstName = f.Name.FirstName(Gendar.Female);
            fa.LastName = f.Name.LastName(Gendar.Female);
        }
    ))
    .RuleFor(u => u.UserName, (f, u) => f.Internet.UserName(u.FirstName, u.LastName))

上記のような定義を作った場合には以下のように生成します。

testUsers.Generate("Male");

// UserName も生成するためには default のルールも含める
testUsers.Generate("default,Male");

私は以下のような拡張メソッドを作って default を含めるケースを書いています。

public static class FakerExtensions
{
    public static T GenerateWithDefault<T>(this Faker<T> obj, string ruleSets) where T : class
        => obj.Generate($"default,{ruleSets}");
}

関連するモデルの生成

この辺はまだ試行錯誤中ですが、 RuleFor に書けるところは書いたり、 FinishWith にまとめて書いたりしています。
こういうちょっとした部分はやはり factory_bot はよくできているなと思います。というか Bogus がそういう用途ではないというのもあると思いますが。

Rule のデータを生成時に書き換えたい時

factory_bot なら特に苦労なく build(:user, first_name: 'test') のように上書きできたのですが、 Bogus の場合はそうはいきません。

今の所は変更することの多いプロパティのみを FinishWith で上書きする Build メソッドを用意したり、イレギュラーな場合は普通に生成後に上書きしたりで対応しています。

例ですが、上記の例を私が作成している Factory class として書くと以下のような感じです。

public static class UserFactory
{
    private static Faker<User> Default = new Faker<User>()
        .RuleSet("Male", set => set.Rules((f, fa) => 
        {
            fa.FirstName = f.Name.FirstName(Gendar.Male);
            fa.LastName = f.Name.LastName(Gendar.Male);
        }))
        .RuleSet("Female", set => set.Rules((f, fa) => 
        {
            fa.FirstName = f.Name.FirstName(Gendar.Female);
            fa.LastName = f.Name.LastName(Gendar.Female);
        }))
        .RuleFor(u => u.UserName, (f, u) => f.Internet.UserName(u.FirstName, u.LastName));


    public static Faker<User> Build(string userName)
        => Default.FinishWith((f, u) => {
        {
            u.UserName = userName;
        });
}

// 利用側
var user = UserFactory.Build("exampleUserName").GenerateWithDefault("Male");

と、いろいろ試行錯誤はしているのですが、今の現場だと NUnit の作法通りにデータセットを淡々と記述しているケースがほとんどなので、「だいぶ邪道なやり方を自分は選んでしまったのではないか。。」という感じで、これからどうしていこうかなぁと悩んでいる最中です。

久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - dotnet script -

dany1468.hatenablog.com

上記の記事の続きです。

といいつつ、上記では触れていなかった話題です 😅

Rails Console が欲しい

Ruby on Rails で開発・運用をしていて一番便利だったと記憶しているのが Rails Console でした。
今の現場の人に話すと「リモートデバッガで済む話じゃない?」とか「SQL 書けばいいのでは」とか言われるのですが、Rails Console のアプリ内のコードをそのまま実行できる感じは、単に ActiveRecordSQL を実行するだけという感じとは違って、調査やデータ抽出においてもかなりパワフルだったなと感じています。(もちろん、エンタープライズになれば、そうそう Rails Console を production 環境で実行とかできないし、実際できなくなってはいくのですが、それでも)

C# の REPL 環境

.NET Framework, .NET Core のいずれでもいくつか REPL は存在します。 ( 検索すれば csi.exe とか C# Interactive とか出てくると思います)

私は Rails Console のように、production でも使えるようなものが欲しかったので、IDE への依存がなく、さくっとインストールできるものが良かったので、.NET Core の global tool としてもインストール可能な filipw/dotnet-script を利用しています。

dotnet script

github.com

README に全部書いてあるので詳細は省きますが、dotnet script では、 csx ファイルだけでなく、 dll の参照や NuGet パッケージのインストールも実行中に可能です。

私の場合は以下のような context.csx を用意し、 dotnet script context.csx -i でプロジェクトを操作できる状態で REPL を起動するようにしています。

#r "プロジェクトの dll"

using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Data;
using System.Data.Common;
using Dapper; # プロジェクトの dll に依存しているものなら読み込める
using Npgsql;

using プロジェクト固有の名前空間

# 設定ファイルへの依存をうまく扱えない場合には環境変数からも設定できるようにしておき、ここで解決させる
Environment.SetEnvironmentVariable(プロジェクトの実行に必要な環境変数);

# 場合によってはショートカット用の関数や設定済みの変数を用意しておくと便利
public ....
{ 

}

ちょっとしたツールにも使いやすい

.NET Core なら dotnet run で実行できるのでさほど差はないのですが、ちょっとした運用ツールも dotnet script の csx + α ぐらいで書いておくと、仮に動かなくてもその場で REPL を起動して試しながら直しながら動かしていけるのでいいのではないかと思っています。

正直、それまでは「実行ファイルだと動かない時面倒だし、とりあえず Ruby で書いておくか。。」みたいな気持ちで小さなスクリプトを書くことが多かったのですが、 dotnet script を使いだしてからは C# で書くようになりました。(C# の現場なので、当然そうすべきなのですが)

その場合は、設定系は環境変数から読めるようにしておき、開発中は direnv を使っています。(私は IDE での作業以外は WSL 上で作業をしているため)

csi.exe を使う場合

csi.exe は今は使っていませんが利用を検討していた時は以下のような bat ファイルを用意して使っていました。

 "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\csi.exe" /r:System /r:System.Core /r:Microsoft.CSharp /r:Syste     m.ValueTuple.dll /u:System /u:System.IO /u:System.Collections.Generic /u:System.Console /u:System.Diagnostics /u:System.Dynamic /u:System.Linq /u:System.Linq.Expressions /u:System.Text /u:System.Threading.Tasks @context.rsp /i context.csx

csi.exe の場合は rsp ファイルでコンテキストを分けて定義するので、 rspcsx を分けています。この辺は Visual StudioC# Interactive が使っている rsp とかを見つつ作っていましたが、調べるといろいろ出てきます。

久しぶりに C# での開発現場に戻ってからテストコードを書く時にやっていること - Respawn を使う -

dany1468.hatenablog.com

上記の記事の続きです。

RDB を使ったテストを書きたい

Ruby on Rails ではデフォルトで Model に対するテストでも RDB を使ったテストを書くことができます。

一方で、私が担当している C# コンソールアプリはテストも無いので当然ながらそういう仕組みはありません。

そこで、まずは以下を目標にしました。

  • テストの AAA でいうところの Arrange でテストデータを RDB に物理的に入れる
  • テストの実行後に RDB からデータを全て削除する

Rails では DB Transaction を使う方法もありますが、今回のアプリは Npgsql を使って直接 SQL を実行するような感じなので、 DB Transaction をテスト時に操作するのは困難であったため、上記のように単純な手法を選ぶことにしました。

この記事でフォーカスする課題は、上記目標の後者であるテスト実行後のデータ削除に関してです。

DatabaseCleaner が無い

Rails を使っていると DatabaseCleaner/database_cleaneramatsuda/database_rewinder を使って、テストの実行後にテストで利用したデータを削除する仕組みを簡単に作れます。

社内の他の C# のコードの中には一部 RDB を利用したテストを書いているケースがあるのですが、そこではおそらく複数人で共通の RDB を開発用に使うためか、明示的に削除するためのコードを書いているものばかりでした ( CI を実行する場合も同様)
もちろんそれでもいいのですが、Rails の現場に居たことで、その手法が面倒に感じてしまいました。

Respawn を使う

github.com

README より引用
Respawn is a small utility to help in resetting test databases to a clean state. Instead of deleting data at the end of a test or rolling back a transaction, Respawn resets the database back to a clean checkpoint by intelligently deleting data from tables.

Respawn は Checkpoint と呼ばれる削除のための定義をコード上で作成した時点で以下のコードにあるようにテーブルの一覧を取得し、加えて対象となる schema や削除対象の絞り込みを行います。

https://github.com/jbogard/Respawn/blob/master/Respawn/DbAdapter.cs

database_rewinder が INSERT を監視して、追加されたデータだけを削除するのと対比すると、 Respawn の削除はテーブル単位であるため、細かく削除定義を設定しないとテストに全く関係の無いテーブルのデータも削除することになるので無駄があるように見えます。
一方で、テストにおいて速度を考えるのはそれが問題になってからで良いと思うので、ひとまずは私の課題は解消されました。

現状は schema ぐらいしか削除定義では指定しておらず、テストに関係の無いテーブルも対象にしてしまっていますが、速度に問題が出てくるようになればテスト毎に削除定義を細かく設定することになりそうです。
ただ、そうなるとテストコードに「このテストはこれらのテーブルに依存している」というのをモロに記述することになるので、実装が漏れ出しているように感じてしまいますが、そこは仕方ないのかなと思います。

他の方法

Npgsql を利用する前提で考えると、 Npgsql が LoggingProvider を登録できるので、 INSERT を監視する LoggingProvider を作成し、そこからテスト後に削除すべきテーブルを取り出すのもいいかと思っています。

テスト対象が Entity Framework のような ORM を利用していれば、Hook を利用するなど、取れる方法が広がるのかもしれません。