テスト駆動開発モドキ実践

過去のブログを掘り起こしたらこんなエントリを見つけました。

単体テスト(ユニットテスト/UT)で考えること - kntmr-blog

どうすれば、メンテし易く保守性の高いソースコードが書けるか、というようなことを考えていた頃のエントリだと思われます。所謂、『テスト駆動開発 (TDD)』と言えるレベルのものではないですが、改めてここで自分なりの方針を少し整理してみようかと。

前提

仕事では Java を使うことが多いため、Java/JUnit あたりのサンプルを記載していますが、これらの考え方は言語やフレームワークに依存するものではないと考えています。

背景

これまでに参画したプロジェクトにおいて、次のようなケースにたびたび遭遇した。

設計もそこそこに突貫で作るようなことが多く、結合試験のフェーズでモグラ叩きのようにデバッグしたり、デグレと闘いながら機能追加したりと、プロジェクトによってマチマチではあるが、かなり綱渡りな状況であったと思われる…。

モノによってはユニットテストコードはあるものの、1メソッド内にモック化や assert のコードが大量に書いてあったり、とても継続的にメンテできるようなものではない。そもそもテストとして機能していないことすらある。(丸々コメントアウトされてたり)

課題

早い段階でバグの芽を摘み、変更に強く、拡張に優しい開発を実現するにはどうすればよいか、開発中に抱える不安感をどうやって解消すればよいか。

そのような思いから、自分なりにきちんとユニットテストコードを書いて、品質が担保できていることをある程度自動化して保証することを目指してみようかと。

課題は、どうすればテストし易いコードになるか、ということ。

アプローチ

コードを書くときはテストし易い粒度でメソッドを分割する。

1つのメソッドの処理が長くなるとテストはやり辛くなる。テストし辛いコードはメンテすることが困難になる。メソッドの命名に悩んだり、メソッド名の文節が極端に多い場合、その処理には何かしらの複雑さが含まれている可能性があると考える。ただし、処理の目的を明確にするために細かく命名することは問題ない。

実装にコードを書き始めるときは、最初にスケルトンコードを書いて全体構成を整理する。メソッドのアクセス修飾子はデフォルト (パッケージプライベート) にして、プロダクションコードのクラスとテストクラスを同じパッケージに配置する。

TDD の世界では「テストから先に書く」ということを言われているが、そのあたりのアプローチがきちんとイメージできていない…。とりあえず、プロダクションコードを書いてからテストを書くようにしている。ただし、プロダクションコードに合わせてテストを書いていたら身も蓋もないので、テストコードを書くときは設計書なりをインプットにすること。

テストコードでは基本的にすべての分岐を通す。また、テストコードはテスト対象のクラス内で閉じるようにする。テスト対象クラスが他のクラスに依存する場合、そのクラスはモックにする。入力値として、境界値や空、および null のケースをテストする。

原則、1つのテストメソッドは、3フェーズ、もしくは4フェーズで構成する。(大抵は tear down を除いた3フェーズで事足りているが…)

  1. set up
  2. exercise
  3. verify
  4. tear down

モック化や assert が極端に多い場合には、プロダクションコードのリファクタリングを検討する。

修正や機能追加

修正や機能追加でプロダクションコードを変更する場合、以下のアプローチを取る。

  1. テストを実行してパスすることを確認する
    (まず、この時点でテストが正しく動作することを確認する)
  2. テストコードを目的の仕様に合わせて変更する
  3. テストを実行して落ちることを確認する
    (想定通りに落ちることを確認する)
  4. プロダクションコードを変更する
  5. テストを実行してパスすることを確認する

ここで気になるのは、テストコード自体が間違っていないか、ということ。人間なのでどうしてもミスをすることはある。(コピペミス然り…)

ミスを起こしにくくする、もしくはミスがあってもすぐにリカバリーできるように、テストコードやプロダクションコードをシンプルに保つ。テストし易いコードを書くように常に意識することが大事。

特長

このようなアプローチを取ると開発にテンポが生まれる。(ような気がする)
また、テストがパスしている間は大丈夫、という安心感を得られることは大きい。

その他

『学習テスト』という考え方があります。

gihyo.jp

テスティングフレームワークを使って、API やライブラリの使い方を調べるというアプローチです。例えば…

Boolean.valueOf("true"); // => true
Boolean.valueOf("TRUE") // => true?
Boolean.valueOf(""); // => false?
Boolean.valueOf(null); // => false? ぬるぽ?

こういうときに簡単なテストコードを書いてみます。

@Test
public void test() {
    assertTrue(Boolean.valueOf("true"));
    assertTrue(Boolean.valueOf("TRUE"));
    assertTrue(Boolean.valueOf("True"));

    assertFalse(Boolean.valueOf("false"));
    assertFalse(Boolean.valueOf("FALSE"));
    assertFalse(Boolean.valueOf("False"));

    assertFalse(Boolean.valueOf("hoge"));
    assertFalse(Boolean.valueOf(""));
    assertFalse(Boolean.valueOf(null));
}

昔は、検証用に Eclipse のプロジェクトを用意して、main から叩いて標準出力とかで確認したりしてましたが、今は JUnit を使ってやっています。

上の記事にも書いてありますが、API やライブラリの使い方を調べる以外に、メソッド単位でロジックを切り出してテストコードから検証します。ある程度、検証できたものはそのままプロダクションのコードに移したりします。

Java 9 を使えば、API やライブラリの検証程度なら JShell で十分なのかもしれません。(たぶん)

まとめ

当然、これが正解ではないですが、自分でも何が正解なのか分からないんですね…。とりあえず、自分がやれることを考えてやってみた結果です。

ただ、もっといいやり方がないものか、とは思います。以上です。

JSUG勉強会 2017年その4 ~ Springを使ったバッチアプリケーション特集 に行ってきた

JSUG勉強会 2017年その4 に行ってきました。簡単に所感をまとめます。

jsug.doorkeeper.jp

今回のテーマは Spring を使ったバッチアプリケーション特集です。

バッチは地味だが役に立つ

www.slideshare.net

  • 処理モデル
    • タスクレットモデル
      • 1ステップに対して1タスクレットの構成
      • シンプル
      • 処理をまとめたい場合など
    • チャンクモデル
    • 処理モデルはジョブごとに選択する
  • 起動方法
    • 同期実行
      • スクリプトなどからジョブを叩く
      • 複数のジョブを組み合わせるケースなど
    • 非同期実行
      • Webコンテナ経由でジョブを叩く
      • バッチ実行に即時性が求められるケースなど (ユーザ操作とか)
  • データ入出力
    • ファイル/DB
    • Bean定義の中でファイル読み込みやDB書き込みの定義が書ける
    • MyBatis
  • フロー制御
    • Job と Step でフローを制御する
    • シーケンシャルフロー
      • 後続ステップを指定してステップを繋げていく
      • リターンコードで分岐を定義できる
    • step の同時実行 (並列処理/多重処理) が可能
  • ジョブ管理機能
    • 障害発生時のリスタート制御
    • 二重起動防止
  • Webコンテナによる非同期実行の課題
  • ファイルアクセスにおける課題
    • Spring Batch では1行を1レコードとして扱う
    • マルチバイト文字が正しく処理できない
    • CSVエスケープ処理してない (クォートで囲った中のカンマが区切りとして見なされる)
  • TERASOLUNA Batch Framework

  • 業務DBを別途用意する場合、2フェーズコミットが必要になるか

    • ジョブの実行結果は JobRepository に入っているのでどこまで処理できたかは分かるはず
    • 2フェーズコミットにしても不整合が発生する可能性はある
    • 2フェーズコミットは使わず、不整合が発生した場合の運用対処を用意するのもアリ

www.slideshare.net

  • JJUG CCC 2017 Spring の再演
  • 現在のレポートシステムは第3世代
    • RabbitMQ + Spring Cloud Stream で構成されている
    • 第2世代が Spring Batch アプリだったので Spring Boot アプリに移行し易かった
    • Spring Cloud では Spring Boot アプリであることが前提
  • Spring Cloud Stream
    • 各サービスがマイクロサービス
      • サービス間はメッセージで連携する
    • Publisher / Subscriber
  • ポーリング型からイベントドリブンにすることでスループットが向上
  • Source (Publisher) -> RabbitMQ -> Sink (Subscriber)
    • output と input 両方で同じ destination を指定する
      • ただし、このままだとスケールしたすべての Sink にジョブが送られてしまう
    • consumer groups でグループ化する
      • グループ内の1つの Sink にジョブが送られるようになる
  • 大量のジョブを処理すると、Job管理テーブルに同時に大量のINSERTが発生する
    • RDBがハングした結果、RabbitMQにメッセージが戻ってくる
      • 空いてる Subscriber に再送する
    • デフォルト3回リトライしてだめだったらメッセージが消える
      • とりあえず上限値を5に上げる
    • Dead Letter Queue
      • 5回リトライしてだめならエラー処理用のサーバにメッセージを送る
      • Dead Letter Queue からメッセージを再送信する
  • API, Batch の Spring Boot 化
    • API : war -> jar (with Jetty)
    • Batch : 1アプリを1jarにする (機能単位でjarにする)
      • 常駐バッチは spring.batch.job.enabled=false にする (起動時にジョブを実行しない)
    • Spring Boot アプリを Web アプリとして起動しない
      • ポートの管理が大変
      • 1サーバに複数プロセスとする
      • spring.main.web-environment=false を設定する
        • Spring Boot 2.0 以降は非推奨
        • 代わりに WebApplicationType を使う

TERASOLUNA は、フレームワーク本体だけでなくドキュメントも充実してるので、とても助かります。これまで Tasklet 構成のバッチしか作ったことがないので、機会があればチャンクモデルも使ってみたいです。Spring Batch は、その処理モデルに合わせて設計するのがキモな気がする。(特にチャンクモデルの場合)

Spring 徹底入門の Spring Batch 編をダウンロードしたけど、まだちゃんと読めてないので勉強しよう。

これまで、Spring Cloud を使ったことがないので、JSUG の勉強会で Spring Cloud 関連のテーマになると理解が追い付かない...。

フレームワークや開発ツールを取り入れることの意義とは

ここ数年、JavaScript フレームワークの盛り上がりを感じます。いろいろと適用事例を聞く機会が増えてきました。

が、最近、JavaScript フレームワークを用いた SPA をやめて、サーバーサイドレンダリングに戻したという話をいくつか聞きました。

なんとなく、揺り戻し のような現象が起きているように思えます。

なぜそういうことが起きるのか、自分なりの考えを簡単にまとめてみます。もちろん、すべてのケースに当てはまるわけではないですが。

そもそも、フレームワーク (アプリケーションフレームワークや開発手法など) や開発ツールを取り入れる際、それによって解決したい問題や課題はなにか を明確にすることが重要です。

で、冒頭で挙げた近年の JavaScript フレームワーク事情においては、「新しい技術である」「流行りの技術である」という理由で「とりあえずこれを使ってみよう」というような短絡的な判断を下しがちになっているように思えます。もちろん、流行りに追従して新しい技術をおさえることは大切ですし、世間でいろいろ取り沙汰される故、焦りを覚えることもあると思います。私もそうです。

ただ、ここでよくないと思うのは、いわゆる 手段が目的になっている ような状態です。

しかし、新しいことを取り入れた直後というのは、得てして上手くいっているように見えるものです。不思議なことに。ところが、本来解決するべき問題や課題を明確にしていないと、いずれは頓挫して上手くいかない状況に陥り、方針を見直す必要性が出てきたりするものです。これが揺り戻しの正体ではないかと考えています。(とはいえ、方針を見直したところで実際には移行できないことが多いかもしれませんが...)

解決したい問題や課題はなにか、普段、開発や運用をする上で不安に感じていることはなにか、どうやったらその不安を取り除けるのか、このあたりをあらかじめ明確にしておくと、どのようなフレームワークや開発ツールが相応しいのか、正しい判断ができるようになると思います。もしかしたら、全体適用ではなく部分適用で十分かもしれないし、いくつかの手法を組み合わせて取り入れることでよりよい効果を生み出せることがあるかもしれません。

また、このようなプロセスを経て改善したものは、メンテし易く、長期に亘って効果を生み出すものだと思います。これは、理に適った改善であり、本来解決するべき問題や課題が解決されるためです。

このように、手段ではなく目的を中心に据えて、解決するべき問題や課題を解決することによって不安を取り除くことが、フレームワークや開発ツールを取り入れることの意義である、と考えています。

現場からは以上です。

Eclipse の Team Synchronizing パースペクティブが開かない

Eclipse でプロジェクトのメニューから Team > Synchronize with Repository とすると、今まで Team Synchronizing パースペクティブが開いていたが、新しい開発環境を構築したら、ふとした拍子にパースペクティブが開かなくなってしまった。そんなときのメモ。

※ Mars.2 Release (4.5.2)

  1. Window > Preferences > Team
  2. Open the associated perspective when a synchronize operation completesAlwaysPrompt をチェックする

Never がチェックされているとパースペクティブではなくビューとして開く。

おそらく、最初にプロンプトが出たときに間違えて Remember my decision をチェックして No をクリックしたと思われる。そうすると Never で設定される模様。まぁ、ビューでもいいんだけど、パースペクティブの方が見慣れているので。

GitHubとクラスメソッドの勉強会に行ってきた #github_method

GitHubとクラスメソッドの勉強会に行ってきました。簡単に所感をまとめます。

classmethod.connpass.com

今回のテーマは「GitHub x AWS の最新 DevOps 事情」です。

  • DevOps はインフラ自動化やリリース自動化など、ひとによって捉え方は違う
  • 価値を無駄なく遅滞なく届けることが重要
  • かつてはスコープに対してスケジュールやリソースを決めていた
  • 近年はこの関係が逆転して、スケジュールとリソースがある上でスコープを決める
  • VCSによるコラボレーションをやりやすくする
    • 中央集権型から分散型へ
  • CI は必須のベストプラクティス
  • Continuous Delivery : 継続的にデリバリーする
  • Continuous Deployment : 継続的にデプロイする
  • GitHub の開発者は世界中にいる
    • それでも1日のデプロイは100回に及ぶことも
    • それを支えるのは GitHub FlowHubot (ChatOps)
  • master を常にデプロイ可能に保つ (本番とイコール)
    • 本番にデプロイして問題がないものを master にマージする
      1. master から feature を作ってコミットを加える
      2. PR を作って開発者みんなでレビューする
      3. レビューで問題がなければ PR ブランチを本番にデプロイする
      4. 本番で問題がなければ master にマージする
    • 本番にデプロイした PR ブランチに問題があれば master をデプロイする
  • master にマージしてからデプロイすると問題があった場合に master が不安定な状態になってしまう
  • 1回本番にデプロイされると、問題ないことが保証されるまでは次以降のデプロイはロックされてキューイングされる
  • Chat は Shared Console である
    • オープンなコンソール (みんなで見ることができる)
    • 誰がやっても同じ結果になる
    • ゆくゆくは AI が絡んでくるかな?

GitHub Flow は2011年頃に出てきたものですが、中のひとたちは割と早い段階で今の運用に変えていたようです。当時、日本にはそのあたりの情報があまりなかったため、池田さん自身もチーム開発実践入門を執筆した段階ではご存知なかったらしいです。
また、1回でデプロイされる変更ボリュームは数十行程度。敢えて小さくしている。1日のトータルは3000行くらいだが、一気にデプロイするのではなく、30行を100回に分けてデプロイするような感じ。とのこと。

www.slideshare.net

  • DevOps の目的はビジネスに使う時間を増やすこと
    • 決まりきった運用や手運用を自動化する
    • 障害対応の時間を減らす
  • CodeDeploy ではデプロイとプロビジョニングを混ぜないように注意
    • プロビジョニングは OS やミドルウェアのための設定
    • プロビジョニングとデプロイではライフサイクルが違う
    • CodeDeploy ではプロビジョニングはしない
  • AWS を使うならマネージドサービスを使うことを第一に検討する
    • 安定性やサービス連携、運用負荷を減らす観点から
    • 標準のサービスでは要件に合わない場合には自前で作る
  • GitHub Enterprise を使う場合は標準で提供している設定や機能を使う
    • アップデートで /data/user 以外が上書きされるため
    • 運用負荷を減らす

AWS については圧倒的経験不足のため、CodeStar をはじめ、多くの DevOps 関連のサービスが提供されていることを初めて知りました...。

正規表現メモ

CODEPREP で正規表現を学びつつ、練習問題の3パターンをメモる。

codeprep.jp

電話番号にマッチさせる

  • 電話番号は3ブロックの数字の並びで構成される
  • 各ブロックの桁数は 3桁、3桁 or 4桁、4桁
  • 各ブロックは - または ( or ) で区切られる
  • 先頭のブロックの数字は0で始まる
  • 先頭または末尾に空白や数字以外があることは許容しない
/^0\d{2}[-(]\d{3,4}[-)]\d{4}$/

URLからホスト名を抽出する

  • URLは http:// または https:// で始まる
  • ホスト名の後ろに / がない場合もある
/https?.{3}([^/]*)/

http:// または https:// で始まるということは、http の後ろに s があってもなくてもよいということと同義である。正規表現では https? で表す。

ホスト名は、http(s):// 以降で最初に現れる / より前の部分を抽出する。つまり、http(s):// 以降で / 以外の文字が続く限りキャプチャする。正規表現では ([^/]*) で表す。

HTML要素の属性値を抽出する

  • a 要素から href 属性の値をキャプチャする
  • = の前後には空白が入ることがある
  • 属性値は " または ' で括られる
  • href 以外の属性が定義されることがあり、順序は不定である
/<a\s*.*href\s*=\s*(["'])(.*?)\1.*/

空白があるかないかは、正規表現では \s* で表す。

シングルクォートとダブルクォートの両方に対応する場合は ["'] で表す。また、後ろのクォートは前のクォートと同一である必要があるため、前のクォートをキャプチャして \1 で後方参照する。キャプチャを複数定義するため、抽出結果は配列の2番目(\2)を取る。

href 以外の属性が存在し得るため、同じクォートが複数回現れることがある。キャプチャは (.*?) で最短マッチにする。

Cookpad Tech Kitchen #7 に行ってきた #cookpad_tech_kitchen

Cookpad Tech Kitchen #7 に行ってきました。簡単に所感をまとめます。

cookpad.connpass.com

今回のテーマは『理想の開発現場の「ふつう」のお話』です。資料はこちら。

  • 開発者もテストエンジニアも品質を向上するという共通の目的を持つ
    • テストの現場の理想形のひとつ
  • 日々の小さな会話がよいチームを作るのではないか
    • フレーズ→振る舞い→価値観
  • 「うまくいったらどうなるの?」
    • ゴールまでの仮説を立てる
    • うまく迷える (ゴールまでのステップが分かっていれば迷っても安心)
  • 「なんでやるんだっけ?」
    • 作業の目的や作業自体の意味を考える
    • やりすぎを抑える
  • 「やりたくないの?」

    • 「やりたいくない」がチームに認められる
    • やりたくないことをチームで共有する
  • 「なんでやるの?」という問答は1対1かもしれない

    • その場にいるみんなが考えるきっかけになる
    • それぞれの認識にズレがあるのかないのかを知ることができる

「なんでやるの?」はやりすぎを抑えられる、というのはよく理解できます。何かに没頭したり集中するあまり物事の優先度の意識が薄れて、つい本質から外れたり、ときには手段が目的になっていたりすることがあります。そういうときに「なぜやるのか?」を自身に問いかけることで、目的を再認識して軌道修正をかけることができると思います。

今回、「明日から使える」というのが登壇者のテーマだったようです。で、実際にすぐに現場に適用できるかどうかというと、当然現場の状況にもよりますが、大半は難しいかも、というのが正直な印象です。

どなたかの質問でも話が挙がりましたが、今回の話のような内容がチームのメンバーに対して効果を発揮するのは、ある程度完成されているチームに限るような気がします。

事業会社では必然的に似たような目的意識を持ったメンバーが集まるのかもしれませんが、受託開発を行う会社では多様なメンバーでチームが構成されることが多いかと思います。(偏見かもしれませんが...)
当然、メンバー全員が当事者意識を持っているかというと決してそうではないだろうし、そのようなチームに対してどのような振る舞いがよい作用をもたらすか、ということを一様に述べることはなかなか難しいのではないかと思います。そのようなチームでは、まずは目的意識を共有できるようにチームの「風土作り」から始める必要があると考えます。しかし、今回はそのあたりは前提から外れているように感じました。

理想の開発現場の「ふつう」のお話というテーマでしたが、そこで「ふつう」に行われていることは、多くの「理想とは程遠い開発現場」にとっては他でもない『理想』そのものであるわけです。

ただまぁ、こういう話をきちんと自分の中で腹落ちさせてチームに共有するところから始めることが、よいチームの風土作りの第一歩になるんだろうと思います。そういう意味でいろいろな気付きが得られたとてもいい機会でした。