PEACOCK MEETS UP #01 に参加してみた #PeacockMeetsUp

そろそろオフラインの勉強会に参加してもいいかなーという気持ちになってきていたので、これに参加してみました。

最初は、「勉強会の空気がよくわからないからおとなしく雰囲気を感じるくらいにしよう」と思ってたのですが、久しぶりに人前で話したい、という気持ちを抑えられなくなってきたのでやっぱり LT 発表しちゃいました。

発表内容

元ネタは事前に作ったやつの焼き直しではあるのですが、テストについて発表させてもらいました。

テストを惰性で書くんじゃなくて、

  • 変化しないテストを書く
    • 人生2回目の「mock 使うの控えよう」ムーブです
  • 明確なテストを書く
    • テスト対象メソッドの挙動すべてを1つのテストメソッドの verify に含めてしまうと挙動が増えたときにどこのテストメソッドに書けばいいか迷子になるから、挙動毎にテストメソッドを書いて1つだけ verify する

みたいなことを述べさせてもらいました。偉そうに書いてますが、これからこういうことを心がけていくよ、というお気持ち発表です。

結論: 楽しかった!

やっぱりオフライン勉強会は聞いてる人の反応が見られるので良いですね。

もともと長岡にはNDSというITの勉強会があって、そちらによくお邪魔させてもらってたのですが、今回はあまりメンバーの知らない勉強会に飛び込んでみました。自分より若い方とも話ができたし、勉強会はやっぱり良いですね!

この勉強会を主催している kinocoboyさんは長岡に移住で来てくれた方で、それだけでもありがたいのに勉強会まで企画してくれて...感謝しかありません。他にも新潟に拠点を移す方も多数いらっしゃることがわかったので、そういう人たちと盛り上げて行きたいなーと思いました。

祝辞をパソコンで印刷する

PTA 会長に回ってくるお仕事に「祝辞」があります。

文章を考えないといけないのですが、それよりも、紙で残すことが気になってました(数年前の祝辞を見せてもらったのですが、どれもきれいな手書き(毛筆)で...。

字が下手な私。どうしたもんかな、イマドキは A4 の横書き印刷で良いかな、と思ったのですが、プリンタ対応の式辞用紙売ってるんですね。

テンプレートをダウンロードして A4 で印刷すればいい感じに作れます。Mac でも大丈夫でした。

1点注意するとするなら、↓だとプリンタによってはうまくいかないかもしれません。

私は最初こっちを買って、家のプリンタで印刷できないじゃん、と慌ててしまいました。 (あやうく対応しているプリンタに買い換えるか、まで考えました...w)

Quarkus + Doma 2 + Lombok を使う

f:id:nemuzuka:20190714095600p:plain

Doma 2が好きなので Quarkus でも使いたいと思って、これ を参考にサンプル作ってたのですが、Lombok と組み合わせると build が通らなくてハマってました。 どうも、Lombok が生成するメソッドが見つからないと怒られる。 で、ここ で聞いてみたわけです。すると、

Lombokアノテーションプロセッサが動いていないようですね。

と、@nakamura_to さんからコメントもらいました。

これで勝つる!

参考コードはこちら

これからも Doma 2 使っていこう。

ソフトウェア品質を高める開発者テスト

「知識ゼロから学ぶソフトウェアテスト」の著者である高橋さんの本です。 「そうそう」「わかるわかる」と頷きながら読み進めて、あっという間に読み切ってしまいました。 私が気になったところと共感できたところを書き出していきます。

上流品質を向上させる

そもそも私は、「上流品質」という言葉、知りませんでした。 個人的には上流/下流工程とかいう響きがあまり好きではないのですが、開発初期でも品質向上を上げる努力をしましょうってことなんですかね。

プロジェクトのしわ寄せは大体コーディングの後のテストフェーズで起こります。 それまでは順調と報告を受けていたプロジェクトもだんだんと暗雲立ち込めてきます。 下手をすると保守フェーズになってもバグが見つかり、システム利用者に迷惑をかけ、信頼貯金がなくなっていく...。

ほんと日本のソフトウェア開発現場はヤバい。

この一言に尽きると思います。わざわざリリース期日に近付いてから手戻りリスクの高い作業をする、というのはリスクヘッジできてない開発組織だと思います。

上流品質を向上させるには

本書では

  • 要求仕様の明確化
  • クラスや関数構造をシンプルに保つ
  • 単体・統合テストの実行
  • レビューの実施

を実行すれば良いと書かれています。 当たり前のようにやっている開発組織もあると思いますが、私もこれである程度の品質は担保できると思っています。 間違っても「システムテストをちゃんとやる」みたいなことではないみたいです。

開発初期でもテストをしなければ、多くのバグを後半で潰すことになり、(間に合わない為)納期を優先することで、潰し切れないバグを残してしまうことになります。 バグを0にするのは難しいですが、システムを利用する上で致命的なバグを出さない為にはこの作業をしておいた方が良いと私も思います。

単体テストで大事なのは網羅率でなく期待値の確認

これは本当にそう思います。 テスト対象のメソッド呼び出しで Exception が起きなければ OK としているテストコードのなんと多いことか。 なんかよく分からんけど正常終了している確認にはなりますし、Exception を throw するような振る舞いに変わった時にデグレに気づくことができるので全く意味がないとは言いませんが、「これでテストはバッチリです!」とは言いたくないなぁと思います。

カバレッジはわかりやすいので目標値として設定しやすいですけど、カバレッジ100%だからと言って品質が高いかというと全然そんなことないです。 境界値や状態遷移で起こりうる入力値のパターンを100%網羅し、その期待値を確認する方が高品質に繋がると私も思っています。

最近のシステムは外部サービス呼び出しも多くあって、単体テスト実行のたびに API 叩くわけにも行かないこともあるでしょうから、その辺りの単体テストは mock することが多いのではないでしょうか。 その時の期待値として、「mock を呼び出したこと」だけでなく、「mock の引数に正しく値を渡しているか」も確認する必要があると思っています。 Java で言うと、Mockitoをよく使うのですが、ArgumentCaptor でテスト対象メソッドから mock に渡したパラメータも確認する必要があると言うことです。 外部サービス呼び出しとか RDBMS に永続化処理があるテストは mock に置き換えて引数をチェックするってことをよくやります。

レビューしよう

しましょう。 レビューは「本人が気づくことに重点をおくもの」だそうです。なので、指摘より「なぜこういうふうになっているの?」という問いかけ形式にすると気づきに繋がるようです。理由によってはコメントした人も気付けるかもしれませんしね。

システムテストの自動化

SIer あるあるですけど、システムテストを自動化するのは夢物語だと思っています。 キャプチャー・リプレイの自動化テストなんか...悪夢でしかないです。 仮にうまくいったとしても、スクリプトのメンテナンスコストがバカにならないと思います。 ちょっとの変更でテストが壊れて、それを放置して自動化しなくなる未来が見えます。

やっぱり単体テストで多くのバグを見つけることが重要

重大なバグをシステムテストで見つける方式はリスクしかないです。それですり抜ける奴があったら...大騒ぎになります。 システムテストはやはり今の時点では手動でやるしかなく、それを毎回人の手でやるってのは苦痛です。 単体テストは自動化しやすく、内部的な状態を再現させやすいので、それに対するテストケースの追加が容易にできます。 バグが出てその修正とテストをセットにしておけば、他の修正でデグレが起きても早い段階で気づけます。 デグレ検知の為に毎回手動でシステムテストをやるとしたら...私はその現場から抜けようと思いますね。

あとがき

薄くて高い!

と読者の方に怒られるみたいですが、そういう方はもっと難しそうな文献を私たちにわかりやすく教えて欲しいものです。 ここまでわかりやすく要約してくれる本、そうそうないと思うんですよね。

まとめ

品質を上げるには特効薬はないんですよね。 要求仕様だって具体的に書かなければテストに落とせないし、テストに落とせないってことは開発完了にならないってことです。それだけでリスクです。 開発したそばからテストも書いておけば、レビューの input にもなるし、デグレ検知もできるしカバレッジも見れるしで「進んでる感」を得られます。

自分の経験を押し付けているように受け取る人がいるようですが、私は解決パターンの引き出しとして良書だと思いました。 他人が経験したものを知ることで同じような失敗しなかったり、成功への近道ができるなんて最高じゃないですか。

あと、まだ売っているらしい。(最近は amazon と中古で売ってるのくらいしか見てないな)

f:id:nemuzuka:20210318182721p:plain

CircleCI実践入門

CircleCI 自体は前から使っていたのですが、知識を update せにゃいかん気になってきたので読みました。 まとまった状態で読めるのはありがたいです。

構成

本書は、なぜ CI / CD が必要か?から始まり、CircleCI の基本、GitHub との連携やジョブが失敗した時のデバッグ方法の説明等がなされています。基本的に CircleCI の機能の説明なのですが、CI / CD のインフラをオンプレ、SaaS どちらにするのかの判断材料として読んでも良いんじゃないかと思います。

気になったところ

CircleCI の機能自体も発見が多かったのですが、ワークフローとジョブの作り方の所が気になりました。

ジョブは分割すべし

最初の方はプロジェクトのコードも小さいので、1つのジョブに

  • 静的解析
  • テスト
  • docker image 生成

みたいな感じで step を連ねて記載していくと思いますが、規模が大きくなってくると、途中で失敗しまうことも多くなります。 長い時間をかけて失敗してしまった時も、リソースを使用した分だけコストが発生します。 再実行する時も最初から実行することになります。 その辺りの余計なコストを発生させないようにするために、それぞれジョブで分けて、実行するのはワークフローで制御する方がベストプラクティスなようです。

確かに規模が大きくなってくると、テストの実行時間を短縮したくなり、並列で実行することを考えます。 その時に1つのジョブになっていると、並列で実行したい内容と1つのプロセスで実行したい内容が混在してきます。 テストだけ並列数を上げて実行したいのに静的解析も並列でやっても意味ありませんし、「他の処理が終わるのを待つ時間」というのもコストになりますから、もったいないですよね。

ジョブ間のファイル共有も考えられていて、ストレージやキャッシュを使って共有できます。

ジョブを小さな責務で分けておくことで、1つのジョブを並列で実行することもできますし、異なるジョブを同時実行することもできます。トータルでかかる時間を減らしやすくなると思いました。これは気にしていこう。

Exit Code 137 問題

私は Java で SpringBoot 使ったアプリケーションのビルドに使うことが多いので、gradle 使うことが多いです。 よく目にするのが、OOM でジョブが失敗する、という奴です。

ジョブを動かすリソースクラスを良いものにすれば大体解決はするのですが、コストが跳ね上がります。 そのために、メモリチューニングする必要があるのですが、GRADLE_OPTS-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2 しましょうと書いてあるのに好感が持てました。確かにサポートページに書いてあるのですが、これだけでも本書を読む価値ありだと思います。

gradle のプラグインによっては、環境変数でなく設定の方でメモリ指定する奴もいるのでご注意くださいませ。

まとめ

CI / CD のインフラ部分を面倒見られる体制があるなら考えなくて良いかもしれませんが、そんな余裕ない or リソースを他のことに回したいのであれば SaaS の検討しても良いんじゃないかと思います。他の SaaS は正直使ったことないのですが、CircleCI は安定していてお勧めできると思います。

目下の悩みは、機能追加に伴ってテストの数も増えていって、CI 回すたびに時間(+SaaS のコスト)がかかる問題の折り合いをどうつけるかなのですが、誰か相談できる人おらんかな...。

Think CIVILITY 「礼儀正しさ」こそ最強の生存戦略である

生存していきたいので読みました。 心理的安全性にもつながってくると思います。気になったフレーズをまとめてみます。

無礼な職場では、半分の人がわざと手を抜く

無礼な態度を取られると気分も悪くなりますが、仕事にかける労力や質を意図的に減らすようです。モチベーション落ちるとやる気なくなりますからね。 職場環境は良くした方が結果がついてきやすいと思います。

ある言動が「無礼」かどうかは相手がどう感じたかで決まる

同じ言動であってもどう受け止めらるかは世代によって違います。 相手が配慮を欠く扱いを受けたと思えばそれはその人にとって無礼な仕打ちを受けたことになります。

理不尽な態度は人の思考力を奪う

周りで怒鳴り声とか聞こえると集中力無くなりますからね。最大限の力を発揮してもらいたいなら無礼な態度を容認しないことが大事ですね。

無礼な人が近くにいると、その影響を受ける

チームリーダーとその上司ががそんな感じで無礼な空気が伝搬している現場があったなぁ...というなんとも言えない気持ちになりました。 直接無礼なことをされなくても居心地はよくなくて、どうにかして早く抜けたい、としか思わなかったです。自分から何か提案しようとは全く思いませんでした。

礼節があるとこんなに良いことが!

仕事が得やすい

確かにそんな気はします。とは言え能力がないと仕事を切られちゃうので難しいところですが...。

良い結果を出す

会社・チーム的な観点からこれは重要ではないでしょうか。 礼節があると、誰かがミスをしても自分の意思で率先して行動を起こすようになり、メンバーの精神的な消耗が少なくなります。 失敗しても大事になるまで黙ってることが無くなり、対処する時間を得ることができるはずです。 礼節がないと、責任を押しつけあったり、前向きな対応ができにくい空気な気がします。

礼節で不利になることはない

礼節ある態度をとるだけでその人は良い人に見えます。 いつか自分が困った時も他の人からの助けを受けやすくなるでしょうし、オススメです。 他人を気遣う方が良い結果につながりやすいと思います。 自分の得のためだけに他人を利用する事は、いずれ愛想を尽かされるでしょうね。

まとめ

礼節は事だけじゃなく、普段の生活にも活かせるんじゃないかと思いました。一人で生きていくわけにもいかないので。 無礼な態度をとっていれば無礼な反応がくるでしょうし、礼節ある態度をとっていれば礼節ある反応が来ると思います。 生きていくのであれば無礼な反応されたくないですし、礼節ある態度の方がメリット多そうな気がします。

能力があればその組織で必要とされるでしょう。ですが、必要とされ続けるには礼節が必要になってくるのだと感じました。 これからの組織は、「能力がある」だけが重要でなく、組織の文化を汚染しそうな人間は能力が高くても排除する動きが来るかもしれません。 今所属している組織が未来永劫変わらずに存続する保証もないでしょうから、組織の中で礼節を持って振る舞うことを加味しながら過ごすと今後良い結果につながるかもしれませんね。

「無礼」はウィルス。周囲に感染させるモノなので距離を置いた方が良さそうです。

Micronaut + Doma2 連携(超簡易版)

f:id:nemuzuka:20200611200142p:plain

やっぱり RDBMS と連携したかったのでやってみました。

参考にしたのは

です。

build.gradle

plugins {
  ...
  id 'org.seasar.doma.compile' version '1.1.0'
}

dependencies {
  ...
  annotationProcessor "org.seasar.doma:doma-processor:2.37.0"
  implementation "org.seasar.doma:doma-core:2.37.0"
  implementation 'io.micronaut.flyway:micronaut-flyway'

  runtimeOnly 'io.micronaut.sql:micronaut-jdbc-hikari'
  runtimeOnly "com.h2database:h2:1.4.200"
  ...
}

Doma2 はアノテーションプロセッサで interface から Dao の実装クラスを自動的に作ってくれます。

コネクションプールには HikariCP を使い、データベースのマイグレーションツールとして flyway を使います。 RDBMS は H2 にしましたが、この辺りは適宜変えてください。

LocalTransactionManager 関連

ここ に書いてあるように、Config の実装クラスを定義して、@Singleton で登録します。

DomaConfigFactory

@Factory
public class DomaConfigFactory {

  @Singleton
  public LocalTransactionDataSource localTransactionDataSource(DataSource dataSource) {
    return new LocalTransactionDataSource(dataSource);
  }

  @Singleton
  public LocalTransactionManager localTransactionManager(LocalTransactionDataSource dataSource) {
    return new LocalTransactionManager(
        dataSource.getLocalTransaction(ConfigSupport.defaultJdbcLogger));
  }
}

LocalTransactionManager を @Factory 経由で登録します。

DomaConfig

@Singleton
@RequiredArgsConstructor
public class DomaConfig implements Config {

  private final LocalTransactionDataSource dataSource;

  private final LocalTransactionManager transactionManager; // 1

  @Override
  public DataSource getDataSource() {
    return dataSource;
  }

  @Override
  public Dialect getDialect() {
    return new H2Dialect();  // 2
  }

  @Override
  public TransactionManager getTransactionManager() {
    return transactionManager;
  }
}
  1. LocalTransactionManager は Factory で登録したインスタンスを Inject します
  2. プロパティからとるとか良い感じにしてください。今回は決め打ちにしました

Dao

@DaoConfig

@AnnotateWith(annotations = @Annotation(target = AnnotationTarget.CLASS, type = Singleton.class))
public @interface DaoConfig {}

TaskDao

@Dao
@DaoConfig // 1
public interface TaskDao {

  @Insert
  Result<TaskEntity> insert(TaskEntity taskEntity);

  @Select
  @Sql("SELECT * FROM tasks ORDER BY task_id")
  List<TaskEntity> selectAll();
}
  1. Dao の interface に @DaoConfig を付与することで、アノテーションプロセッサで生成する impl クラスに @Singleton を付与してくれます。

後は、いつものように Doma2 を使えば良いです。

接続情報

RDBMS と flyway 用の設定をします。

datasources:  #1
  default:
    url: 'jdbc:h2:mem:micronaut-sample;LOCK_TIMEOUT=10000;MODE=PostgreSQL'
    username: 'sa'
    password: ''
    driverClassName: 'org.h2.Driver'
    schema-generate: CREATE_DROP
    dialect: H2
flyway:
  datasources:
    default:
      locations: db/migration #2
  1. 接続情報を設定します。schema-generate はテストの時だけ設定します。
  2. flyway が読み込む sql ファイルの配置場所を指定します。

Dao のテスト

TaskDaoTest

@MicronautTest // 1
@Property(name = "flyway.datasources.default.locations", value = "db/migration,db_fixtures/minimum") // 2
class TaskDaoTest {
  @Inject private TaskDao sut; // 3

  @Inject private LocalTransactionManager transactionManager; // 4

  @Test
  void testInsert() {
    transactionManager.required( // 5
        () -> {
          // setup
          var task = new TaskEntity(UUID.randomUUID().toString(), "タスク名", "内容");

          // exercise
          var actual = sut.insert(task);

          // verify
          assertThat(actual.getCount()).isEqualTo(1);
          assertThat(actual.getEntity()).isEqualTo(task);
          assertThat(sut.selectAll()).hasSize(3);
        });
  }

  @Test
  void testSelectAll() {
    transactionManager.required(
        () -> {
          // exercise
          var actual = sut.selectAll();

          // verify
          assertThat(actual).hasSize(2);
          assertThat(actual.get(0).getTaskId()).isEqualTo("001-dummy");
          assertThat(actual.get(1).getTaskId()).isEqualTo("002-dummy");
        });
  }
}
  1. @MicronautTest をつけることで、Inject できるようにします
  2. テスト開始時に flyway で table 作成するだけでなく、fixture となるデータを sql で入れたかったので、プロパティ flyway.datasources.default.locations を上書きます
    • db_fixtures/minimum は test/resources ディレクトリに配置しています。
  3. Dao を Inject することでアノテーションプロセッサで生成した Dao の実装クラスが Inject されます。
  4. LocalTransactionManager を Inject します
  5. Inject した LocalTransactionManager を使用してテストします。Dao のテストはトランザクションをテストクラスで制御しています。参考

トランザクション

@Transactional

アノテーショントランザクションを制御できるようにします。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Around
@Type(TransactionInterceptor.class)  // 1
public @interface Transactional {}
  1. @Transactional がついている時はトランザクションを張るようにします

TransactionInterceptor

@Singleton
@RequiredArgsConstructor
public class TransactionInterceptor implements MethodInterceptor<Object, Object> {

  private final LocalTransactionManager transactionManager;

  @Override
  public Object intercept(MethodInvocationContext<Object, Object> context) {
    return transactionManager.required((Supplier<Object>) context::proceed); // 1
  }
}
  1. 今回は手を抜いて、propagation は REQUIRED だけにしました

TaskUseCase

@Singleton
@RequiredArgsConstructor
@Transactional  // 1
public class TaskUseCase {

  private final TaskRepository taskRepo;

  ...
}
  1. 今回は UseCase に @Transactional を付与しました
    • このメソッド呼び出しが正常終了すると commit し、Exception を throw すると rollback します

まとめ

micronaut で doma2 を使ってアクセスできるようになりました。 Micronaut Data に Doma2 を...とも思ったのですが、簡単にいかなそうなので小手先でできるようにしてみました。Java の ORM 何が流行ってるのかわからん。

若干やっつけ感はありますが、アノテーショントランザクション管理できるようになりました。 今回の差分は大体こんな感じです。

*1:超感謝です!