kakakakakku blog

Weekly Tech Blog: Keep on Learning!

TDD のリズムを体験しよう /「テスト駆動開発」を読んだ

今年の1月に書籍「テスト駆動開発」を購入し,1度ザッと読んだけど,まだ書評記事を書いてなく,後回しにしていたらもう9月になってしまった.最近「テスト駆動開発 (TDD : Test-Driven Development)」を教える機会もあるため,改めて本書を読み直し,学べたことを整理しておこうと思う.

本書は TDD を支える「Red / Green / Refactor」のリズムから,「仮実装」「明白な実装」「三角測量」など,重要な戦略の解説もある.そして「多国通貨」を題材とした TDD の体験と「xUnit」を実際に実装しながら学べるように構成されている.個人的に TDD は「名前は知っているけど経験したことがない」人が多くいるプラクティスだと思う.本書を読んで第一歩を踏み出すと良さそう!

目次

  • 第I部 : 多国通貨
    • 第1章 : 仮実装
    • 第2章 : 明白な実装
    • 第3章 : 三角測量
    • 第4章 : 意図を語るテスト
    • 第5章 : 原則をあえて破るとき
    • 第6章 : テスト不足に気づいたら
    • 第7章 : 疑念をテストに翻訳する
    • 第8章 : 実装を隠す
    • 第9章 : 歩幅の調整
    • 第10章 : テストに聞いてみる
    • 第11章 : 不要になったら消す
    • 第12章 : 設計とメタファー
    • 第13章 : 実装を導くテスト
    • 第14章 : 学習用テストと回帰テスト
    • 第15章 : テスト任せとコンパイラ任せ
    • 第16章 : 将来の読み手を考えたテスト
    • 第17章 : 多国通貨の全体ふりかえり
  • 第II部 : xUnit
    • 第18章 : xUnitへ向かう小さな一歩
    • 第19章 : 前準備
    • 第20章 : 後片付け
    • 第21章 : 数え上げ
    • 第22章 : 失敗の扱い
    • 第23章 : スイートにまとめる
    • 第24章 : xUnitの全体ふりかえり
  • 第III部 : テスト駆動開発のパターン
    • 第25章 : テスト駆動開発のパターン
    • 第26章 : レッドバーのパターン
    • 第27章 : テスティングのパターン
    • 第28章 : グリーンバーのパターン
    • 第29章 : xUnitのパターン
    • 第30章 : デザインパターン
    • 第31章 : リファクタリング
    • 第32章 : TDDを身につける
  • 付録
    • 付録A : 因果ループ図
    • 付録B : フィボナッチ
    • 付録C : 訳者解説:テスト駆動開発の現在

仮実装

第1章「仮実装」を読むと「TDD のリズム」をすぐに感じられる.意図的に「歩幅を小さく」し,テストを通すための最小限の変更と言える「仮実装」を活用しながら実装を進めていくため,実装の変化に悩むことなく読み進められる.テストを通すために int amount = 10; を実装したり,重複を発見するために int amount = 5 * 2; に書き直してみたり,初期化部分を times() メソッドに移動してみたりする.まさにリズム!

  1. 小さいテストを1つ書く
  2. 全てのテストを実行し,1つ失敗することを確認する
  3. 小さい変更を行う
  4. 再びテストを実行し,全て成功することを確認する
  5. リファクタリングを行い,重複を除去する

さらに本書の特徴とも言える「口語のような口調」も非常に良く,実際にライブコーディングを見ているような錯覚に陥る.

class Dollar {
    int amount = 10;

    Dollar(int amount) {
    }

    void times(int multiplier) {
    }
}

明白な実装

「仮実装」を使わずに既に考えた実装をコードに落とし込むことを「明白な実装」と言う.第2章「明白な実装」Dollar オブジェクトの状態変化に依存するため,times() メソッドから新しく Dollar オブジェクトを返している.そして TDD では「仮実装」「明白な実装」の間を行き来するという内容も紹介されている.「なぜ "あえて" 仮実装をするの?」というよくある疑問に対する回答になっている.

三角測量

「仮実装」をしていると,たまたまテストが通ってしまうことになるため,別のテストを追加することにより,実装の正しさを確認する戦略として「三角測量」がある.第3章「三角測量」を読むと,Value Object である Dollar オブジェクトに equals() メソッドを実装しながら三角測量を活用している.また本書に載っている通り「設計のアイデアが浮かばないとき」にも三角測量が使えるというのは興味深かった.

public class MoneyTest {
    @Test
    public void testEquality() {
        assertTrue(new Dollar(5).equals(new Dollar(5)));
        assertFalse(new Dollar(5).equals(new Dollar(6)));
    }
}

歩幅

通貨の実装を進めると,誰しも通貨を表現するオブジェクトを作りたくなると思うけど,最初から複雑な実装に進むのではなく,最初は「文字列を活用する」という判断こそ「歩幅を小さく」の具体例と言える.第9章「歩幅の調整」を読むと,Franc.javaDollar.javacurrency() メソッドを追加し,文字列で CHFUSD を返す実装をしてから,コンストラクタに移動したりし,最終的に Money.java にまとまる.TDD の良さを感じられる章だと思う.

リファクタリング

リファクタリングはアプリケーション開発をしているとよく聞く言葉だけど,案外「リファクタリングの定義は?」と聞かれると,人によって違う回答になる.第31章「リファクタリング」を読むと,TDD で使えるリファクタリングのアプローチを学べる.例えば,以下など.

  • データ構造の変更
  • メソッドの抽出
  • メソッドのインライン化
  • メソッドの移動

他にも,書籍「リファクタリング」の第3章「コードの不吉な臭い」に載っている観点も TDD のリファクタリングに使える.合わせて読もう!

TDD を身につける

第32章「TDDを身につける」は個人的に役立った.TDD を教えるときによく聞かれることに対して,全て明確な答えがないとしても「テスト駆動開発」に載っていると言えるのは説得力に繋がる.

例えば,最初に「一歩の大きさはどのくらいか」という疑問が出てくる.本書には「テスト駆動開発者の傾向は "小さなステップ " だけど "大きなステップ" もできるようになるべき」と書いてある.第9章「歩幅の調整」と同じ内容だけど,ステップを飛ばすとしても,自信を持って飛ばせるまでは過剰にでも繰り返すと良いと思う.他にも以下のような疑問に対する言及がある.

  • テストしなくてよいものはあるか
  • どのようなときにテストを消すべきか
  • 巨大なシステムをテスト駆動できるか
  • 途中から TDD に乗り換えるにはどうすればよいか

Cyber Dojo

本書には直接関係ないけど,環境構築に悩むこともなく,サクッと TDD を練習したり,教えたりする場合は「Cyber Dojo」を使うと便利.Python (unittest)Python (pytest)Ruby (RSpec) など,プログラミング言語とテストフレームワークを選び,Fizz Buzz などのお題を選ぶと,すぐに開始できる.

「Cyber Dojo」を使って TDD のサイクルを回すと,実行ごとに「赤 / 黄 / 緑」の結果アイコンも表示されるため,歩幅を小さく進められていることを確認できるのも便利な機能と言える.TDD に限らず「モブプログラミング」の実演にも使える.覚えておくと良いかと!

cyber-dojo.org

まとめ

  • 書評記事を書いていなかった「テスト駆動開発」を読み直した
  • 「多国通貨」を題材とした TDD の体験は実際に写経をしながら進めると効果的に学べる(僕は途中まで写経した)
  • 特に「TDD は知っているけど経験したことがない」という読者層に刺さりそう
  • 本書の最後に載っている「付録C : 訳者解説」もとても印象に残った

あと個人的には,第12章「設計とメタファー」Expression オブジェクトを使って為替レートを考慮した通貨間の計算をする部分は難しく感じた.というよりも,突然「歩幅が大きく」なったような気がして,読んでいてもスッと頭に入らなかった.もう少し「歩幅を小さく」解説があると良さそうに思う.

関連記事

kakakakakku.hatenablog.com

計画メンテナンス時に便利な Mackerel の新機能「ダウンタイム機能」を試した

先週 Mackerel で新機能「ダウンタイム機能」が使えるようになった.簡単に言うと「日時を指定して監視と通知を停止する機能」となり,サービスで計画メンテナンスが必要な場合など,運用上とても助かる機能と言える.今までは Mackerel ホストのステータスを WorkingMaintenance に切り替えて運用していたため,機能の差を確認するためにも「ダウンタイム機能」を試してみた.

mackerel.io

基本設定 / ダウンタイム期間

Mackerel メニューから「Monitors → ダウンタイム設定 → 新規ダウンタイムを作成」に遷移すると,以下を設定できる.特に「繰り返し設定」は便利で「間隔」として「年 / 月 / 週(曜日指定可能)/ 日 / 時間」を細かく設定できる.さらに「ダウンタイム終了日時」も設定できる.

  • 基本設定
    • 名前
    • メモ
  • ダウンタイム期間
    • ダウンタイム開始日時
    • ダウンタイム期間
    • 繰り返し設定
      • 間隔

f:id:kakku22:20190921165759p:plain

絞込

さらに「ダウンタイム設定」を適用する対象を絞り込むこともできる.「サービス / ロール」を指定すれば機能ごとにメンテナンスできるし,「監視ルール」を指定すればメトリクスごとに無視できる.さらに「除外条件」を指定することもできるため,柔軟なダウンタイム設定を実現できる.

  • サービス / ロール
    • 絞込条件
    • 除外条件
  • 監視ルール
    • 絞込条件
    • 除外条件

f:id:kakku22:20190921165811p:plain

検証

実際に「ホスト死活監視 (connectivity)」「ホストメトリック監視 (CPU %)」を発生させたけど,「ダウンタイム設定」の期間中にアラートは発生しなかった.「ダウンタイム設定」の役割を終えると,設定ごと消える挙動も確認できた(繰り返しの場合は残る).

フィードバック

以下は少し気になる.フィードバックとして残しておこうと思う.

  • 過去に設定した「ダウンタイム設定」を確認するために履歴を残せると良さそう(コピー機能も)
  • mkr から設定できると良さそう
  • 「ダウンタイム開始日時」のフォーム幅が狭くて「秒」が隠れている

今まで使っていた mkr update --status maintenance

1年半前に Mackerel Meetup に登壇したときに Mackerel ホストのステータスを mkr で自動的に切り替える仕組みを紹介した.mkr update --status maintenance は複数ホストもサポートしていて便利だけど,やはり「サービス / ロール」を指定できるのは「ダウンタイム機能」のメリットになりそう.適材適所かなと思う.

$ mkr update --status  maintenance xxx
   updated xxx
$ mkr update --status  maintenance yyy zzz
   updated yyy
   updated zzz

f:id:kakku22:20190921172535p:plain

Mackerel Meetup の参加レポートは以下にある.もう1年半も前なのかぁ...!

kakakakakku.hatenablog.com

まとめ

  • Mackerel の新機能「ダウンタイム機能」を試した
    • ダウンタイム期間 / 繰り返し設定 / 絞込などを設定できる
    • 計画メンテナンスなど活躍する場面も多そう

引き続き「Mackerel アンバサダー」頑張るぞー👍

Material-UI の List コンポーネントを実装する

先週「React + TypeScript + Material-UI」を使ってプロトタイプ開発をする機会があった.今まで Material-UI は使ったことがなかったけど,ドキュメントを読むとコンポーネントも多く用意されていて,非常によくできていた.React で Material Design を簡単に実装できる.

material-ui.com

今回は Material-UI の List コンポーネントをサンプルコードを参考に実装しながら理解を深めていく.

material-ui.com

なお,実装したサンプルコードは GitHub に公開してある.TypeScript で create-react-app を実行してから実装を進めた.記事に載せるコードはポイントを限定し抜粋するため,実際にコード全体を見る場合は GitHub を参照して頂ければと!

$ create-react-app sandbox-material-ui-list --typescript

github.com

List / ListItem / ListItemText コンポーネント

まず,リストを実装する場合は List コンポーネントを使う.List コンポーネントに component プロパティを指定すると,例えば navdiv など,リストを表現する要素を決められる.

List コンポーネントに含める項目1個1個は ListItem コンポーネントを使う.代表的なプロパティとして以下を指定できる.

  • button : ボタンとして押せるようになる / デフォルト false
  • disabled : 非活性にして選択できないようにする
  • selected : 最初から選択状態にする
  • divider : リストに下線を引く
  • component : 要素を決める

詳細は以下に載っている.

リストに文字列を表示する場合は ListItemText コンポーネントを使う.primary プロパティに文章を指定する.

List / ListItem / ListItemText コンポーネントを使ってリストを実装した.紹介したプロパティも一部使っている.キャプチャに載せた「List 6」ListItem コンポーネントに a 要素を指定した関数を実装し ListItemLink として使えるようにしている(GitHub 参照).クリックすると kakakakakku blog に遷移する.

return (
  <div className={classes.root}>
    <List component="nav">
      <ListItem>
        <ListItemText primary="List 1" />
      </ListItem>
      <ListItem button>
        <ListItemText primary="List 2 (button)" />
      </ListItem>
      <ListItem disabled>
        <ListItemText primary="List 3 (disabled)" />
      </ListItem>
      <ListItem selected>
        <ListItemText primary="List 4 (selected)" />
      </ListItem>
      <ListItem divider>
        <ListItemText primary="List 5 (divider)" />
      </ListItem>
      <ListItemLink href="https://kakakakakku.hatenablog.com/">
        <ListItemText primary="List 6 (link)" />
      </ListItemLink>
    </List>
  </div>
);

f:id:kakku22:20190915143303p:plain

ListItemIcon コンポーネント

ListItemIcon コンポーネントを使うとリストにアイコンを追加できる.

Material-UI は「Material Icons」としてアイコン自体も配布しているため,コンポーネントとして使える.よって,以下のようにアイコンを import し,ListItemIcon コンポーネントと組み合わせることができる.

import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';

material-ui.com

ListItemIcon コンポーネントを使ってアイコン付きリストを実装した.「List 7」「List 8」はより直感的になった.

return (
  <div className={classes.root}>
    (中略)
    <List component="nav">
      <ListItem button>
        <ListItemIcon>
          <CloudUploadIcon />
        </ListItemIcon>
        <ListItemText primary="List 7 - Upload" />
      </ListItem>
      <ListItem button>
        <ListItemIcon>
          <CloudDownloadIcon />
        </ListItemIcon>
        <ListItemText primary="List 8 - Download" />
      </ListItem>
    </List>
  </div>
);

f:id:kakku22:20190915143320p:plain

ListItemAvatar / Avatar コンポーネント

ListItemAvatar コンポーネントと Avatar コンポーネントを使うと写真をプロフィール画像のように表現できるようになる.Avatar コンポーネントには alt / src / className プロパティなどを指定できる.さらに ListItemText コンポーネントに secondary プロパティを指定するとリスト2行目に文章を追加できる.

ListItemAvatar / Avatar コンポーネントを使ってプロフィール画像とイニシャル画像(個人的に Gmail Inbox でよく見た)を実装した.イニシャル画像の場合は backgroundColor を指定した makeStyles() を定義している(GitHub 参照).

return (
  <div className={classes.root}>
    (中略)
    <List component="nav">
      <ListItem button>
        <ListItemAvatar>
          <Avatar alt="kakakakakku" src="https://pbs.twimg.com/profile_images/604918632460656640/FdOmiWZW_200x200.png" />
        </ListItemAvatar>
        <ListItemText primary="List 9 - @kakakakakku" secondary="https://kakakakakku.hatenablog.com/" />
      </ListItem>
      <ListItem button>
        <ListItemAvatar>
          <Avatar alt="kakakakakku" className={classes.orangeAvatar}>K</Avatar>
        </ListItemAvatar>
        <ListItemText primary="List 10 - @kakakakakku" secondary="https://github.com/kakakakakku" />
      </ListItem>
    </List
  </div>
);

f:id:kakku22:20190915143334p:plain

ListItemSecondaryAction コンポーネント

ListItemSecondaryAction コンポーネントを使うとリストをカスタマイズできる.

今回は ListItemSecondaryAction コンポーネントと Checkbox コンポーネントを組み合わせて,選択可能なリストを実装した.実際にアクションを含める場合,ドキュメントに載っているように handleToggle() などを実装し onClick() から呼び出すようにする.

return (
  <div className={classes.root}>
    (中略)
    <List component="nav">
      {[1, 2, 3].map(value => {
        return (
          <ListItem button>
            <ListItemAvatar>
              <Avatar className={classes.purpleAvatar}>{value}</Avatar>
            </ListItemAvatar>
            <ListItemText primary={`List ${value + 10} - User ${value}`} />
            <ListItemSecondaryAction>
              <Checkbox />
            </ListItemSecondaryAction>
          </ListItem>
        );
      })}
    </List>
  </div>
);

f:id:kakku22:20190915143347p:plain

まとめ

  • Material-UI を使うと React で Material Design を簡単に実装できる
  • 今回はドキュメントを読みながら List コンポーネントを実装しながら理解を深めた
  • 他にも多くのコンポーネントがあり,プロトタイプ開発のためにも調べたいと思う

github.com

アプリケーションとインフラのパフォーマンスをモニタリングしよう /「入門 Prometheus」を読んだ

今年5月に出版された「入門 Prometheus」を読んだ.本書は Prometheus の仕組みから,実際に本番環境で運用するときに必要になるデプロイの観点まで解説されているため「Prometheus に興味のある幅広い読者層」にオススメできる1冊だった.僕自身も Prometheus を本番環境で運用した経験はなく,Kubernetes や Microservices のハンズオンを試しながら,合わせて Prometheus と Grafana を使う場面が多く,本書の読者層に合っていた.

本書は O'Reilly Japan 様より献本を頂き,ありがとうございます!そして,本書の監訳者である @superbrothers さん,出版おめでとうございます!本書を読んでいたら,多くのページに「監訳注」が入っていて本当に素晴らしかった!原著出版後に追加された Prometheus API も紹介されていて,とても読みやすくなっていた.予想以上に本書のボリュームが大きく,さらに実際に Prometheus 環境を構築し,試しながら読み進めたり,個人的に忙殺されていた時期もあり,書評記事の公開が予定よりも遅れてしまったという点は反省点と言える.

目次

  • 第Ⅰ部 : イントロダクション
    • 1章 : Prometheusとは何か
    • 2章 : 初めてのPrometheus
  • 第Ⅱ部 : アプリケーションのモニタリング
    • 3章 : インストルメンテーション
    • 4章 : 開示
    • 5章 : ラベル
    • 6章 : Grafanaによるダッシュボードの作成
  • 第Ⅲ部 : インフラストラクチャのモニタリング
    • 7章 : Node exporter
    • 8章 : サービスディスカバリ
    • 9章 : コンテナとKubernetes
    • 10章 : よく使われるexporter
    • 11章 : ほかのモニタリングシステムとの連携
    • 12章 : exporterの書き方
  • 第Ⅳ部 : PromQL
    • 13章 : PromQL入門
    • 14章 : 集計演算子
    • 15章 : 二項演算子
    • 16章 : 関数
    • 17章 : レコーディングルール
  • 第V部 : アラート
    • 18章 : アラート
    • 19章 : Alertmanager
  • 第Ⅵ部 : デプロイ
    • 20章 : 本番システムへのデプロイ

Prometheus とは?

まず「1章 : Prometheusとは何か」を読むと Prometheus の基本を学べる.SoundCloud 社によって開発された話 / CNCF (Cloud Native Computing Foundation) / モニタリングの必要性もまとまっている.そして Prometheus のアーキテクチャを整理し,本書に出てくる様々な Prometheus 用語の概要も学べる.具体的には以下など.

  • exporter
  • サービスディスカバリ
  • スクレイプ(プル型)
  • インストルメンテーション
  • Alertmanager
  • PromQL
  • Grafana

そして「2章 : 初めてのPrometheus」では,さっそく Prometheus を実行し,Prometheus 自体のメトリクスを取得する.次に Prometheus コンソール http://xx.xx.xx.xx:9090 にアクセスし,PromQL を使って upprocess_resident_memory_bytesrate(prometheus_tsdb_head_samples_appended_total[1m]) などのクエリを体験する.さらに Node exporter を実行し,Linux メトリクスを取得する.もし Prometheus を試したことがなければ,最初に検証用の Prometheus 環境を構築し,雰囲気を掴んでから本書を読み進めると効率的に学べると思う.

f:id:kakku22:20190911104232p:plain

なお,今回は「Prometheus 2.12.0」を検証環境にした.

prometheus.io

Prometheus クライアントライブラリ

「3章 : インストルメンテーション」では,アプリケーションのメトリクスを Prometheus でスクレイプする.本書では Python のクライアントライブラリである prometheus_client を使って,アプリケーションを実装する.以下のサンプルコードはスクレイプする HTTP Server を実行している.なお,本書に載っているサンプルコードは GitHub に公開されているため,写経しても良いし,GitHub からコピーもできる.

github.com

import http.server
from prometheus_client import start_http_server

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")

if __name__ == "__main__":
    start_http_server(8000)
    server = http.server.HTTPServer(('localhost', 8001), MyHandler)
    server.serve_forever()

Python コードを実行したら,prometheus.yml に以下の定義をする.Prometheus を再起動をしておく.

scrape_configs:
  - job_name: example
    static_configs:
      - targets:
        - localhost:8000

すると,HTTP Server をスクレイプし,PromQL を使って python_info のメトリクスを取得できるようになる.実際にアプリケーションに組み込む場合は「カウンタ」「ゲージ」「サマリ」「ヒストグラム」などを実装し,より価値のあるメトリクスを取得する.さらに「メトリクス名のサフィックス」に慣習があるという話もコラムにあって参考になった.

python_info{implementation="CPython",instance="localhost:8000",job="example",major="3",minor="7",patchlevel="4",version="3.7.4"}

f:id:kakku22:20190911104401p:plain

Pushgateway とは?

バッジジョブなど,スクレイプできないターゲットからメトリクスを取得する場合に「Pushgateway」を使う.注意点として Pushgateway は Prometheus をプル型からプッシュ型に変えるものではなく,Prometheus は Pushgateway をスクレイプする.よって,Pushgateway も exporter と言える.今まで Pushgateway を使ったことがなく,本書を参考に試した.

まず,Pushgateway を実行する.

$ wget https://github.com/prometheus/pushgateway/releases/download/v0.9.1/pushgateway-0.9.1.linux-amd64.tar.gz
$ tar -xzf pushgateway-0.9.1.linux-amd64.tar.gz
$ cd pushgateway-0.9.1.linux-amd64
$ ./pushgateway

次に prometheus.yml に以下の定義をする.Prometheus を再起動をしておく.

scrape_configs:
  - job_name: pushgateway
    honor_labels: true
    static_configs:
      - targets:
        - localhost:9091

今度は prometheus_clientpush_to_gateway() を実装し,Python コードを実行すると,メトリクスを Pushgateway に送信できる.

from prometheus_client import CollectorRegistry, Gauge, push_to_gateway

registry = CollectorRegistry()
g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
g.set_to_current_time()
push_to_gateway('localhost:9091', job='batchA', registry=registry)

最後は Pushgateway コンソール http://xx.xx.xx.xx:9091 にアクセスすると,スクレイプされたメトリクスを確認できるようになる.

f:id:kakku22:20190911113232p:plain

サービスディスカバリ

Auto Scaling を前提にすると,毎回 prometheus.ymlstatic_configs を定義するのは運用的に難しく「サービスディスカバリ」を検討する必要がある.「8章 : サービスディスカバリ」では,Prometheus における「サービスディスカバリ」の仕組みを学べる.サービスディスカバリも本書を参考に試した.

まず,最初に「ファイルサービスディスカバリ」を試す.下のように prometheus.ymlfile_sd_configs を定義し,JSON もしくは YAML を指定すると,Prometheus の再起動をせず,自動的に読み込まれるようになる.Dynamic な環境だと運用的に難しいけど,prometheus.yml との依存度を減らせるメリットがある.

scrape_configs:
  - job_name: file
    file_sd_configs:
      - files:
        - '*.yml'

そして,任意の YAML ファイルを作成し,targets の中にターゲットを指定すると,Prometheus 側で確認できるようになる.今回は検証のために 10.10.10.10 は意図的に存在しないターゲットにしている.

- targets:
    - 10.0.1.84:9100
    - 10.0.2.104:9100
    - 10.10.10.10:9100
  labels:
    service: web

f:id:kakku22:20190911104444p:plain

より Dynamic に Amazon EC2 を対象にする場合,prometheus.ymlec2_sd_configs を定義する.さらに relabel_configsTag を定義し,タグによる絞り込みを定義できる.Prometheus で非常に重要な機能である「ラベル」に関しても,本書に多く解説があり,参考になった.実際に Dynamic な環境で Prometheus を運用するときに,もう1度読み直そうと思う.

scrape_configs:
  - job_name: ec2
    ec2_sd_configs:
      - region: ap-northeast-1
        access_key: xxx
        secret_key: xxx
        port: 9100
    relabel_configs:
      - source_labels: [__meta_ec2_tag_environment]
        regex: prd
        action: keep
      - source_labels: [__meta_ec2_tag_role]
        regex: web
        action: keep

デプロイ

最後の章「20章 : 本番システムへのデプロイ」では,Prometheus を本番環境で運用することを前提に,理解しておくべき知識を整理できる.特に Prometheus を階層化する「フェデレーション」や,時系列データを長期保存するために Prometheus API から「スナップショット」を取得する機能は今まで使ったことがなく,参考になった.個人的に「ダウンサンプリング」など,Prometheus のメトリクスを意図的に中長期に残していく話にも興味があったけど,本書にはメトリクス数を減らす設定と scrape_intervalevaluation_interval を変更する設定などが載っていた.Thanos の事例など,別途調べてみようと思う.

prometheus.io

誤植

O'Reilly のサイトにまだ「正誤表」がなく,気付いた誤植を載せておこうと思う.今回読んだのは「初版第1刷」となる.

  • P.201「この章では、の書き方を学ぶ。」
    • exporter の記述が抜けている

www.oreilly.co.jp

まとめ

  • 「入門 Prometheus」を読んだ(献本ありがとうございます!)
  • 「Prometheus に興味のある幅広い読者層」にオススメできる1冊だった
  • 実際に Prometheus 環境を構築し,試しながら読み進めると効率的に学べると思う

Python + unittest を使ってテストパターンをパラメータ化するときに subTest() メソッドを使う

Python + unittest を使って TDD (Test Driven Development) の練習(ペアプログラミング)をしていたときに,リファクタリングのサイクルでテストパターンをパラメータ化することになった.Parameterized Test にリファクタリングをするために,期待値をパラメータ化し,for の中に assert を実装した.以下はサンプルコードとなり,インプット文字列を大文字に変換する挙動を確認している.さらに「意図的に」ce を誤った期待値に設定し,失敗するようにした.なお,今回は Python 3.7 を検証環境にした.

import unittest


class MyTestCase(unittest.TestCase):
    def test_upper(self):
        patterns = [
            ('a', 'A'),
            ('b', 'B'),
            ('c', 'c'),
            ('d', 'D'),
            ('e', 'e'),
        ]

        for lower, upper in patterns:
            self.assertEqual(lower.upper(), upper)

テストコードを実行すると,以下の結果となる.期待した通りにテストは失敗しているけど,c の失敗で止まってしまう.

AssertionError: 'C' != 'c'
- C
+ c

FAILED (failures=1)

subTest() メソッドを使う

Python 3.4 で追加された subTest() メソッドを使うと,テストパターンをサブテスト化して区別できるようになる.unittest のドキュメントにも「サブテストを利用して繰り返しテストの区別を付ける」として,サンプルコードが載っている.

docs.python.org

実際に subTest() メソッドを使ってリファクタリングをすると,以下のような実装になる.subTest() メソッドの定義は subTest(msg=None, **params) となり,msgparams にサブテスト失敗時のメタデータを設定できる.失敗時の判断を正確にするために重要なオプションと言える.今回はサンプルとして仕様を書いた.

import unittest


class MyTestCase(unittest.TestCase):
    def test_upper(self):
        patterns = [
            ('a', 'A'),
            ('b', 'B'),
            ('c', 'c'),
            ('d', 'D'),
            ('e', 'e'),
        ]

        for lower, upper in patterns:
            with self.subTest('%s is upper case of %s' % (upper, lower)):
                self.assertEqual(lower.upper(), upper)

テストコードを実行すると,以下の結果となる.

  • サブテストを全て実行してから結果を表示できた(e の失敗まで実行できた)
  • [e is upper case of e] のように失敗時のメッセージを表示できた
  • failures にサブテスト数を表示できた
AssertionError: 'C' != 'c'
- C
+ c

AssertionError: 'E' != 'e'
- E
+ e

One or more subtests failed
Failed subtests list: [c is upper case of c], [e is upper case of e]

FAILED (failures=2)

まとめ

  • Python + unittest を使って Parameterized Test を実装するときは subTest() メソッドを使う
  • ドキュメントを読むと「サブテストをネストできる」と書いてあり,試してみようと思う
  • Python 用に Parameterized というライブラリもあり,1度試してみようと思う

github.com