dunno logs

神山生活五年目

「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 を利用するなど、取れる方法が広がるのかもしれません。

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

久しぶりに C# の現場になる

といっても、2018 年の 2 月から徐々になので、そろそろ 戻ってから 1 年経つというところです。

それまでは Ruby メインの開発現場に 4 年と少しいて、その前は C# をずっと書いていたので久しぶりという感じです。

Ruby から戻って困ったこと

Ruby というよりは Ruby on Rails の現場に長く居たということと、C# に戻って担当したアプリが長年運用されていたもので、それこそテストコードすら存在しないものだったいうのが大きいと思います。

で、何に困ったかというと、、、テストです。

その前にディスクレイマー

  • 筆者は C# でのテストの経験がそれまであまりない。NUnit で簡単なテストを書いていたぐらい。
  • 筆者は Ruby on Rails での現場に長くいて、そのやり方に慣れている
  • 筆者は最近の C# でのテストの書き方に精通しておらず、その状態でプロジェクトに望む必要があった

ということで、以降の内容は Ruby on Rails の現場に居た人間が C# でも Rails みたいにテストコードを書きたいという、ちょっとしたワガママの一つぐらいに思ってもらえればと思います。これが正解だとは微塵も思っていないです。

テストをどう書く?

私が居た Ruby on Rails の現場は、テストに関しては RSpec, Factory Girl を利用しつつ、Model spec, Request spec, Feature spec をまんべんなく書くという感じでした。 Fixture はそれほど使わず、Controller spec もあまり使わないという感じです。

一方で、担当した C# のアプリケーションは 5 年以上前に片手間で書かれたようなもので、それにも関わらず重要なデータの削除を担当していたりして、、とよくある話ですよね。

にも関わらず、結構大規模な修正をしなければならなくなったので、「とりあえず既存機能のリグレッション」のテストを書かなければという気持ちになったのですが、 Rails の現場でやっていたようなテストを書ける環境がさくっとは整わなかったというのが発端です。

具体的には以下のようなものです。

  • テストのセッションごとに隔離された RDB の環境が無い!
  • テストデータを作るための Factory も Fixture も無い!
  • WebMock が無い!
  • DatabaseCleaner が無い!
  • RSpec のように階層のあるテストをどうやって書けばいいのか分からない。。

おそらく現場によってテストの書き方は様々で、特に C# は DI Container もよく使われているようなので Mock を多用する事が多いというのもあるかもしれません。 今回に関しては、古いアプリケーションということもあり、できるだけ End to End (コンソールアプリなので End to End という呼び方でいいのかは分かりませんが)でのテストを書きたいというのもあり、上記のようなテスト環境を揃えたかったというのがあります。

どのように書いていったか

以下ではとりあえず、何を使って対応したかぐらいを書いています。細かいことは別の記事にしていこうと思います。

  • テストのセッションごとに隔離された RDB の環境が無い!
    • Docker Compose を使って共通した CI 用の DB インスタンスを使わずに、テスト毎に RDB の環境を作る
    • これは C# 関係ないですね。
  • テストデータを作るための Factory も Fixture も無い!
    • EF Core を使って、テスト対象の RDB から Entity の class を自動生成
    • Bogus を使って Factory を定義
  • WebMock が無い!
  • DatabaseCleaner が無い!
  • RSpec のように階層のあるテストをどうやって書けばいいのか分からない。。