Magnolia Tech

いつもコードのことばかり考えている人のために。

『systemdの思想と機能 ―Linuxを支えるシステム管理のためのソフトウェアスイート』でsystemdの機能を再確認する

いつの間にかどのLinuxディストリビューションでも標準で使われるようになったsystemd。なんとなくsystemctlを叩いてサービスを立ち上げていたけど、いまいち「なんで必要なの?」とか、「いろいろ機能があるっぽいけど、何ができるの?」といったことをちゃんと勉強しないままここまで来てしまったので、この本でおさらい。

単なる機能紹介ではなく、「なぜsystemdはこんな広範囲な機能を扱うのか?」という機能が作られた背景から説明されているところがいいですね。

この本を読むまでcore dumpまで管理されるようになったことを知りませんでした。


本書を読めば、systemdはサービスが動く環境を用意するために必要な機能を備えていった結果、多数の機能を持つようになった、ということがよく分かります。

プロセスを安全に立ち上げ、維持し、実行状態をモニタリング(監視、ログ)するために必要な一連の機能がすべて揃っていて、ワンストップでsystemdだけで対応することができるところが、その目的だということがよく分かりました

昔は、アプリケーション独自のサービス実行管理は、daemontoolsとかでやってましたね。

systemdのリファレンスとか、実装詳細、コマンドオプションの解説までやるととても終わらないので、全体で200ページ弱の中で駆け足で機能概要と、その必要性が説明されていて、一通り読めばsystemdを使う上でのスタート地点に立てます。その点にフォーカスしているのが本書の良いところ。

サクっと読めます。でもまぁ、それで「systemd、チョットワカル」にはならないところがsystemdの機能の多さなんですけど

特にアプリケーションレイヤーの人は、第10章のjournaldと、第11章のcore dumpあたりを押さえておくと良いと思いました。

BRIMFORD キーキャップ キースイッチ プラーを買ったら最高だった

え!何?なんで今まで買ってなかったの!!

今まで割と簡易なキースイッチプラーを使って、うまくキースイッチを抜けずに苦労していたのだけど、この形のプラー、一発でバシっとハマって、シュッと抜けて最高だ!

キースイッチの交換をする人は、絶対にこの形のキースイッチプラーを買った方がいいよ!!

どんどんキースイッチを買って、どんどん交換する機運か!(待て)

2023年買ったもの(技術書とか)

2023年のお買い物、技術書編です。

技術書以外はこちら

blog.magnolia.tech

とりあえずノータイムで買っちゃえ!損は無いよ!という3冊

他に、類似の本がないか、有ってもこっち買っておけばよくね?という3冊

なっとく!関数型プログラミング

Scalaをベースとした関数型プログラミングの学習本。オブジェクト指向言語であり、関数型プログラミング言語であるScalaの特性を生かして、命令型から宣言型のコードの書き方への変え方を学んでいくスタイル。

前半の凄まじい丁寧な学習のステップと、後半の「ここまで一気にやらなくても良くない?」の落差もすごいけど、ページ数の厚さに躊躇せず、前半1/3くらいを時間をかけて丁寧に学習するくらいが良いと思います。

周りに良い先生が居れば別ですが、そうでなければこの本でじっくり学ぶのはかなりお勧めです。

実践プロパティベーステスト

プロパティベーステストの本がこの先、さらに日本語で出版される日が来るのか分かりませんが、かなり確率は低い気もするので、さっさとこの本を読んで学習しておくと良いと思います。

例題がErlang、Elixirなので、プロダクションコードを書く言語としては決してメジャーとは言えない言語ですが、逆に言えば自分が使っている言語用のプロパティベーステストのライブラリを使って書き換えていく、という学習法が取れるところが良いですね。

ちょうぜつソフトウェア設計入門 - PHPで理解するオブジェクト指向の活用

出版自体は2022年の12月の終わりですが、今年に入っても何度か読み直しましたし、色々な人にお勧めしました。今年の本といってもいいかな。

表紙のかわいさに騙されてはいけません。これ本当にちゃんと理解する気持ちで、コードを書いたり、周辺情報を調査したり、周りの人とディスカッションしながら読み進めたら、平気で1年かかる密度の本です。

現代的なソフトウェアのプラクティスが、ものすごい密度で書かれています。でも表層的に読んで「へーそうなんだ」と思っても、意味はないので自身の血肉となるまで考えながら読むことが大切です。

その分野を知りたい人にはぜひお勧めの本

技術カンファレンスのマスターガイド:企画から運営までの完全手引き

techbookfest.org

残念ながら現時点で入手する方法が存在しませんが、カンファレンスや勉強会を運営する人のためのリファレンスの決定版。長年勉強会を開催してきた人でも、新たな学びや視点がてんこ盛りなので、機会が有ればぜひ読んだ方がいいです。

Functional Programming in Scala, Second Edition

早く日本語版が出ないかなー、出たら色んな人にお勧めするのになーと、邦訳を首を長くして待っている1冊。

Scalaをベースに、関数型プログラミング用のライブラリ(Listとか、Optionとか、Stateとか)を実際に作りながら、その概念を理解する、というストロングスタイルな1冊。

『なっとく!関数型プログラミング』が、表側(利用者視点)から関数型プログラミングを学ぶ本だとしたら、こちらは裏側(ライブラリの提供者側視点)から関数型プログラミングを学ぶ本といえます。 この2冊をセットで読み進めて、そのテストを『実践プロパティベーステスト』による理解を元に、プロパティベーステストで書けばかなりの実力がつくこと間違いなしです。

1st Editionはずいぶん前に日本語訳が出ていて、今でも内容は全然古くなっていないので、Scala3対応や、回答の解説はGitHub上のサンプルコードを読み解きます!という人にはこちらでも大丈夫です。ただ、練習問題の解答例が本誌上に無い中で読み進められる人はなかなか居ないかも……とは思ってしまいました。

スクラムの拡張による組織づくり

スクラム開発をどうやってスケールさせていくか?その方法論はいくつか有るけど、その中でも「Scrum@Scale」をベースにコミュニケーションを軸とした組織の作り方、運用の仕方を解説していく本。

組織論みたいな本、なかなか苦手なのですが、これは開発組織をスケールさせるための本、ということで非常に興味深く読むことができました。

ソフトウェア設計のトレードオフと誤り

個人的には、本書の中心的なテーマであるトレードオフではなく、「7章 日付と時間のデータを効率よく扱う」の内容が一番刺さった。この章、全然トレードオフの話をしていなくて、いかに日付や時刻、時間を扱うライブラリの設計が難しいのか?ということが延々と語られる章になっている。他の章も十分に役に立つのだけど、これだけ身近なテーマである「日付と時間」がここまで大変な設計の上に成り立っているのか!ということはみんな理解しておいた方が良いと思う。

ブログのエントリも凄いブクマを集めたけど、みんなそれだけこのテーマに関心が高い、ということなのだと思っている。

blog.magnolia.tech

レガシーコードとどう付き合うか

若干、タイトルと中身のメインテーマがずれていると思うけど、投資フェーズと組織の開発戦略、採用を絡めて論じる、という切り口がすごく新鮮で、これは(それぞれの会社ごとに状況は違っているかもしれないが)、読んでおいて損の無い1冊だな、と思いました。

オブザーバビリティ・エンジニアリング

どんどんアプリケーションを構成するコンポーネントが増えていくなかで、「監視、つらい」という場面も増えてきました。

本書は、構造化ログ、トレース、OpenTelemetryというキーワードを元に、どうやってアプリケーションのリアルな振る舞いを追いかけ、おかしな挙動を速やかに検知するのか、ということを理解するための本です。

いやー、ほんと、異常終了すればスタックトレースと、アクセスログを見ればよかった時代は、もう遠い過去の世界なんですね、というか、なんでこんなに辛いんだっけ?というと、それを軽減するための仕組みを導入していないだけでは?という話に行き着くので、まずは読んでおきましょう。

マスタリングLinuxシェルスクリプト 第2版

令和最新版のシェルスクリプトの入門書とリファレンスがセットになった1冊。

ブログのエントリでも書きましたけど、なんかコンテナの勉強しているのか、シェルスクリプトの勉強をしているのか自分でも分からなくなってきたので、もう一回腰を据えて勉強するぞ!と思って読みました。

blog.magnolia.tech


Rustとか、コンテナとか、クラウドとか、その辺が全然フォローできていないんですけど、それはそれであくまで自分の興味の向くままに読んだ結果なので。

積んだまま読めていない本も本当にたくさん有るし、読んでも紹介するほどでもないな、と思った本はブログにも書いてませんしね。

というわけで、来年も色々な本を読んでいきたいと思っています。

なお、技術書を除くと、この2冊がよかった。

前者は小沢健二のソロデビュー時のマネジメントや、数々のコンピレーションアルバムの編集をやっていた井出靖さんの視点から見た90年代の東京の音楽シーンの回顧録。 後者は三体の前日譚というか、直接の繋がりは無いけど、いかにも劉 慈欣らしい、静かなムードの中で進むSF。

『井出靖 / Rolling On The Road 僕が体験した東京の1960年代から90年代まで』

『劉 慈欣 / 三体0【ゼロ】 球状閃電』


なお、2022年版はこちら

blog.magnolia.tech

2023年買ったもの(技術書以外)

2023年に買ったもの

技術書以外編

技術書編は、こちら

blog.magnolia.tech

WH-1000XM5

特に説明不要なノイズキャンセルヘッドホンの定番。それまで使っていたWH-1000XM3が完全に壊れてノイズキャンセルどころか、謎のノイズが出てしまう症状が出たため買い替え。

Bluetooth接続が圧倒的に早くなったのと、マルチポイント接続により同時に複数の機器と接続できるようになったのは、割と頻繁に接続先を変える自分はとても利便性が上がったので、それだけでも買い換える価値が有った。

ノイズキャンセルの効きも良くなり、地下鉄や飛行機などの、ノイズが多い移動手段が多い人にはぴったりの一台。

5万円を超える価格はオーディオ機器としてみるとちょっと高いけど、音楽鑑賞から、オンラインミーティングまでこれ一台でなんでもできてしまうので、コストパフォーマンスは非常に高い。一時はほんと朝から晩までずっと付けっぱなしで過ごしていた。

MDR-7506

自宅でのオンラインミーティングなどで、独立マイクも用意できるし、ノイズキャンセルが不要な場所ではこちらを使うようにしている。

有線の煩わしさは有るけど、軽いのと、充電を気にせず使える気軽さもいいし、どんな環境でも「とりあえずプラグを刺せば音が出てくる」という安心感は、とにかく安定しておいてほしいオンラインミーティング環境では必須。

これも複数のPCを使い分けることが多いので、その切り替えごとに、操作をしたくない、という観点で有線の安心感を選択。

ソニーのヘッドホンといえば、MDR-CD900STも惹かれたけど、接続がミニプラグの方が直接PCに挿せて便利なので、MDR-7506を選択。

TimeTimer

年齢が上がったせいか、だんだんと集中力の持続時間が落ちてきているなーと感じることが多くなってきたので、見やすいタイマーを導入。

厳密に、ポモドーロテクニックを使っているわけではないですが、15分とか、30分とか、「この決めた時間の間は集中する!」と決めると、それなりに集中力も持続するので、重宝しています。

何のギミックも無く、ただぐるっと手でツマミを回した分だけ時間を測る簡単仕様。

あと、デザインがかわいくて、机の上に置いておきたくなるデザイン。

Lenovo ThinkCentre M75 Tiny

今年だけで2台も買ってしまった。

www.lenovo.com

ここ数年色々なメーカーからミニPCが発売されていて、あまりの価格の安さに採算取れるのかな?と心配になるのだけど、サポート面などでの不安も聞こえてくる。

そのため、ちょっと価格帯は上がるけど、Lenovo純正の複数年サポートが付けられる安心感を優先して選択。

主な使い方は、プリインストールされているWindowsを完全に消去して、Linuxをインストールして、個人の開発環境用。 やっぱりWSLより、素のLinuxが動く環境が有った方が余計な苦労をしないで済むのが楽だし、一台の高スペックPCより、複数台のミドルスペックPCの方がコストパフォーマンスも良い。

Lenovoの場合、Linuxからでもファームウェアアップデートが可能なところもオススメのポイントの一つ。

若干ファンが煩いので、普段はテレビボードの下にしまい込んでリモートアクセスでしか使わないけど、そういう使い方ができるのもミニPCの良いところ。

Keychron Q60

Keychron Q60 QMK Custom Mechanical Keyboard – Keychron | Wireless Mechanical Keyboards for Mac, Windows and Android

Keychronから出ているHHKB完コピメカニカルキーボード。

本家は、HHKB Studioを出したけど、割とギミック多めで、それは特に要らないかも...という人向け。

とにかくシンプルなデザインがイイ!有線専用のQ60は既に終売になっていて、今は無線と、更なる静音化のために構造が見直されたQ60 MAXが現行モデルになっています。

なお、本家より便利なのは、Win-Macの配列変更のスイッチがあるところで、これが有るだけでも日常的にWindows機と、Macを切り替えながら使っている自分にとっては最高の機能です。 本家のディップスイッチ方式はさすがになーと思っていたので

今のところ、キーキャップと、スイッチを交換していて、この組み合わせの打鍵感が過去最高に気に入っているので、当面はこの組み合わせ使っていくと思います。

Keychron Q60 Max QMK/VIA Wireless Custom Mechanical Keyboard – Keychron | Wireless Mechanical Keyboards for Mac, Windows and Android

Kailh Clione Limacina Switch tactile

CHERRY INDUSTRIAL KEYS

Cherry Industrial Keysnovelkeys.com

Kailh Midnight Silent V2 Switch / Tactile

Kailh Clione Limacina Switch tactileの打鍵感は最高なのですが、周りに人が居るような環境だとちょっと使うのを躊躇するくらいの軽快な打鍵音が響いてしまうので、そんな時は、同じKALIHのMidnight Silent V2というスイッチを使っています。

KEEBMAT Premium Felt Edition

キーボードと、トラックパットをまとめてデスクマットの上に置きたいので、KEEBMATのKEEBMAT Premium Felt Editionというのを導入しました。

KEEBMAT™ Premium Felt Edition (incl. Free Coaster!)

KEEBMATは、小型キーボードのサイズにぴったり合わせたキーボードマットが有名ですが、フェルトエディションの一番大きなサイズは、完全にデスクマットとして使える大きさで、お勧めです。

ただし、アイロンが無いと綺麗に平らにならないので気をつけてください。

Lamy2000 4色ボールペン

定番中の定番ですが、定期的に書い直していて、通算3本目を買いました。

どんなにPCやスマホタブレットが進化しても、ボールペンの書き味からくるフィードバックにはまだ敵わないんですよね。


まだまだ買ったものが有った気もしますが、ついにキーボード沼にハマったな……というのが今年の一番のトピックですね


2023/12/30 追記 はてブでコメントいただいたので追記

  • WH-1000XM5のマイク WH-1000XM3を買ってしばらくしてからコロナ禍が始まり、オンラインミーティングの環境がイマイチ揃えられなくてしばらく不便な音声環境が続いていた。でもある日「あれ?これ、もしかしてノイズキャンセル用のマイクで通話もできるのか!」と気づいて以来、WH-1000XM3をミーティング用に使うことで劇的に通話環境が改善されたので、さらに音声用マイクとしての改善が反映されているというのも、WH-1000XM5に決めた理由の一つだった。

    www.sony.jp

  • HHKBライク配列のMacと、Windowsの日本語入力について

    Windowsの方でのローマ字入力と、英字入力の切り替えを「CTRL+Space」に設定しておくところがポイントです。これでWindowsMacで同じキー操作になります。あとは、Spaceキーの横のキーをWindowsモードの時はALTキー、Macモードの時はCommandキー、その更に横をOptキーになるように配列を変更しておけば完璧です。それぞれのOSに適した配置になるので、操作性がキープできます(そもそもWindowsはCTRL、MacはCommandになっているショートカットは慣れましょう、でしかないですが)


2022年版はこちら

blog.magnolia.tech

『実践プロパティベーステスト』の例題をScalaで解いていく その3

『実践プロパティベーステスト』の例題をScalaで解いていくシリーズの第3回目です。

前回は、テストが失敗して、収縮した結果を確認する、という内容でした。

blog.magnolia.tech

今回は第3章に入ります。


初めてプロパティベーステストを書き始めたとき、サンプルコードだけを見て、「なるほどこう書けばいいのか!」と納得して書き始めたものの、すぐにピタっと手が止まりました。それは、「テスティングフレームワークが提供するAPIは分かった、でもそれを使ってどんなテストを書けば、プロパティベーステストになるのか?」...このとっかかりが分かりませんでした。

色々なテストコードの事例を見ながら、実際に自分で書いてみて少しずつ感覚をつかんでいきましたが、やはり何らかのとっかかりが有ると理解が早く進みます。


第3章「プロパティで考える」では、以下の4つの観点でプロパティベーステストを書くときの観点を紹介しています。

  1. モデル化

    テストしたいコードと同じ振る舞いをするコード(たいてい効率が悪い)と、結果を比較する手法です。 ビジネスロジックのような固有の振る舞いをコードに落とすような場合には、モデル化できるものは少ないと思いますが、汎用的なライブラリや、他の言語の実装から移植などでは有効です。

  2. 事例テストを汎化する

    これが一番分かりやすい取り組み方と言えます。通常のユニットテストを書いてみて、そのパターンを元に抽象化を行い、プロパティベーステストとしてまとめていく手法です。例えばリストの要素がゼロの場合、一つの場合、二つの場合...と増やしていったときに、その長さに依存しないような抽象化ができれば、プロパティベースのテストとして実装できます。

    ユニットテストベースのテストを考え、そこから抽象を見出す……という流れが遠回りにも感じられますが、具体的なテストから出発する分、結局これが一番早く、確実な手法と言えます。

  3. 不変条件

    一つのテストですべてをカバーするのではなく、複数のテストを組み合わせて、そのコードの振る舞いの確からしさを総合的に判断するための手法です。それぞれ分解した観点ごとに、不変条件(必ず満たさなければいけない条件)を特定し、それらがすべてテストをパスすれば、コード全体としての確からしさが確認できた、と言えるところからの発想です。

    何をもって不変条件が網羅したか?と考えるのは非常に難しいですが、一つの指標としてテストのカバレッジによる判断は有効であるといえるでしょう。

  4. 対称プロパティ

    JSONライブラリのような、エンコーダと、デコーダがセットで提供され、変換⇒再変換により元の結果に戻れば、その結果の正当性が確認できる、という手法です。

    適用できる場面は限られますが、適用できる場合は確実な方法です。

Scalaへ移植していく

本の中で紹介されていたbiggest関数をScalaに移植します。

package Pbt

object PbtHelper:
  def biggest[A](list: List[A])(using Numeric[A]): A =

    def go[A](l: List[A], m: A)(using Numeric[A]): A =
      val num = summon[Numeric[A]]

      (l, m) match
        case (Nil, i)    => i
        case (h :: t, i) if num.gteq(h, i) => go(t, h)
        case (_ :: t, i) => go(t, i)

    go(list.tail, list.head)

再帰とパターンマッチで書き直すと、割とさっぱりと書けます。 また、特定の型に依存したくなかったので、比較関数を提供するNumericを継承している型に限定して指定できるようにusing Numeric[A]の指定を入れています。ネストされた内部関数にまでusingは効果を及ぼさないので、内部関数の中でも指定しています。

summonはScala3から導入された関数で、usingの引数に型のみを指定した場合に、具体的なインスタンスを取得します。

この辺りの仕組みは、以下の公式ドキュメントに詳しく書かれています。

docs.scala-lang.org

次にテストを書いていきます。エンコードと、デコードは、ちょっと面倒だったので、circeで雑に済ませてしまいました。

import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
import org.scalacheck.Arbitrary.*
import org.scalacheck.Gen.*

import Pbt.*

import io.circe.syntax.*
import io.circe.Json

object PbtTest extends Properties("Pbt Test"):

  property("最大の要素を見つける") =
    forAll(nonEmptyListOf(arbitrary[Int])): (x: List[Int]) =>
      PbtHelper.biggest(x) == modelBiggest(x)

  def modelBiggest(list: List[Int]): Int = list.sorted.last

  property("最後の数を選ぶ") =
    forAll(listOf(arbitrary[Int]), arbitrary[Int]): (list: List[Int], knownLast: Int) =>
      val knownList = list :+ knownLast
      knownLast == knownList.last

  property("ソート済みリストは整列したペアを持つ") =
    forAll(listOf(arbitrary[Int])): (list: List[Int]) =>
      isOrdered(list.sorted)

  def isOrdered(list: List[Int]): Boolean =
    list match
      case h1 :: h2 :: t => (h1 <= h2) && isOrdered(h2 :: t)
      case _ => true // 2要素未満のリスト

  property("ソート済みのリストはサイズを維持する") =
    forAll(listOf(arbitrary[Int])): (list: List[Int]) =>
      list.length == list.sorted.length

  property("何も要素が追加されなかった") =
    forAll(listOf(arbitrary[Int])): (list: List[Int]) =>
      val sorted = list.sorted
      sorted.forall(x => list.contains(x))

  property("何も要素が削除されなかった") =
    forAll(listOf(arbitrary[Int])): (list: List[Int]) =>
      val sorted = list.sorted
      list.forall(x => sorted.contains(x))
 
  property("対称的なエンコードとデコード") =
    forAll: (l: List[Map[String, Int]]) =>
      val encoded = enocde(l)
      l == decode(encoded)

  def enocde(o: List[Map[String, Int]]): Json = o.asJson
  def decode(j: Json): List[Map[String, Int]] = j.as[List[Map[String, Int]]].getOrElse(Nil)

『実践プロパティベーステスト』の例題をScalaで解いていく その2

blog.magnolia.tech

前回に続いて、ScalaCheckによるプロパティベーステストを書いていきます。

失敗するテストを書く

当然すべてのテストが一度で成功することは有りません(一発で完璧なコードが書けるなら、そもそもテスト書かなくてよくなってしまいます)。

ScalaCheckでテストが失敗するとどうなるか見てみます。

まずは、第2章の例に倣って、テストが失敗するコードを書きます。

def biggest[A](list: List[A]): A =
  list.head

必ず先頭の要素を最大として返却しているので、いかにも失敗しそうです。

対応するテストコードを用意します。標準コレクションのsort機能を使ってソートした結果の最後の要素を最大として取り出します。

property("最大の要素を見つける2") =
  forAll(listOf(arbitrary[Int])): (x: List[Int]) =>
    PbtHelper.biggest(x) == modelBiggest(x)

  def modelBiggest(list: List[Int]): Int = list.sorted.last

実行すると、テストが失敗しました。

sbt:pbt> test
failing seed for Pbt Test.最大の要素を見つける is oSzCiQZYscT-yK1oib2eve7Ls7uAmFa3cNYxkn7YJdK=
[info] ! Pbt Test.最大の要素を見つける: Falsified after 7 passed tests.
[info] > ARG_0: List("0", "1")
[info] > ARG_0_ORIGINAL: List("1", "-1", "1773528742")

ARG_0_ORIGINAL: List("1", "-1", "1773528742")が実際に失敗したテストですが、よりシンプルなARG_0: List("0", "1")というパターンが出てくることで失敗した理由がより分かりやすくなっています。これが収縮です。

『実践プロパティベーステスト』の例題をScalaで解いていく その1

今年読んだ技術書のベスト3を選べと言われたら、間違いなく『実践プロパティベーステスト』を取り上げます。

日本では過去に類似の本も出ていないし、これからもプロパティベーステストだけで1冊の本が出版される可能性も限りなく低いことを考えると、さっさと読んでおいた方がいい1冊と言えます。

記載されているサンプルコードをそのまま写経して動かすだけでも学びはあるけど、やはり何らかの変化が有った方がより学びが深まる。

ちょうどこの本に興味を持つきっかけがScala用のプロパティベーステスティングフレームワークであるScalaCheckに興味を持ったタイミングだったこともあって、ScalaCheckベースでサンプルコードを書き直していきながら、調べたこととかをつらつらと書いていきます。

scalacheck.org


プロパティベーステストは、テスト対象のコードが備えるべき特性(プロパティ)の定義と、それを検証するために利用するテストデータの生成を分離することで、コードを書いた人が想定しなかったコードの不具合を特定するためのテスト手法です。

しかし、抽象的な思考が求められる手法のため、プロパティベーステスト用のテスティングフレームワークのサンプルコードだけでは、なかなか実践的なテストコードの書き方を習得するまで至るのは難しく、とにかく他人が書いたテストコードを読むか、自分で書いてみて発見していくしかなかったので、このような本の登場で実践的な書き方が早く身に付けられる道筋ができたのは素晴らしいことです。

ではさっそく始めていきます。


テスト環境の整備

まずはScalaのScalaCheckの実行環境を整備します。

ScalaCheckは、各種Scala用のテスティングフレームワークの中から呼び出して使うこともできますが、ここではScalaCheck自体をテスティングフレームワークとして使い、sbtからtestコマンドでテストが実行される環境を用意します。

テンプレートからのプロジェクト作成

Scalaの新規プロジェクトの作成方法はいくつか有りますが、ここではsbt newコマンドでGiter8形式のテンプレートから作成します。

% sbt new scala/scala3.g8
name [Scala 3 Project Template]: pbt

Template applied in /path/to/./pbt

% cd pbt

テンプレートから作成されたbuild.sbtにはテスティングフレームワークとしてmunitが指定されていますが、これをScalaCheckに置き換えます。

-    libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test
+    libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.17.0" % Test

また、不要なサンプルコードのファイルを削除しておきます。

% rm src/test/scala/MySuite.scala
% rm src/main/scala/Main.scala

sbtは、最新のバージョンが使われるようにリリースされているバージョンを確認して、必要に応じてproject/build.propertiesを書き換えてください。

Releases · sbt/sbt · GitHub

2023年12月27日時点の最新バージョンは、1.9.8です。

最初のプロパティベーステスト

まずは、「1.4 プロパティを実行する」の例にならって必ず成功するテストを例に始めていきます。

(なお、以降のコードは全てScala 3.3以降でサポートされたFewer Braces記法で書かれいるため、従来のScalaのコードとはずいぶん見た目が異なっています)。

src/test/scala/PbtTest.scala

import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
import org.scalacheck.Arbitrary.*

object PbtTest extends Properties("Pbt Test"):

 property("always works") = forAll(arbitrary[AnyVal]): (a: AnyVal) =>
    boolean(a)

  def boolean(v: AnyVal): Boolean = true

上記のコードをテストとしてsbtから実行します。

$ sbt
...(省略)...
sbt:pbt> test
[info] compiling 1 Scala source to /path/to/pbt/target/scala-3.3.1/test-classes ...
[info] + Pbt Test.always works: OK, passed 100 tests.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 3 s, completed xx xx, xxxx, xx:xx:xx xx

100回のテストに成功し、Pbt Testというテストが成功したことが分かります。

見れば分かるレベルの内容ですが一応解説をしておくと…

  1. Propertiesを継承し、テストオブジェクトを作成

    Propertiesへの引数がそのままテスト全体の名称になります。

  2. 具体的なテスト内容は、propertyで定義

    慣れないとちょっと引っかかる記法ですが、Mapの更新にも使われるupdateメソッドへの糖衣構文と同じです。

    内部実装としては、propertyという変数を経由してPropertySpecifierというクラスのupdateメソッド呼び出しに変換されますが、利用者側では気にする必要はありません。(Map以外で出てくると、ちょっとアレっ?と思う、Scalaの独特な記法だと思っています。)

  3. forAll関数の引数には、ランダムに生成されたテストデータを引数として取り、Boolean型の結果を返すコードブロックを渡します

    標準コレクションでもおなじみのforAll関数は、要素の中に一つでもfalseとなるものがあれば全体をfalseと判定します。つまり、たくさんのテストデータが自動生成された結果、一つでもテストの結果が失敗すればテスト全体が失敗した、とみなされます

    ScalaCheckのプロパティベーステストは、以下の形式で書くことになります。

    forAll(ジェネレータ): (コードブロックへの引数) =>
    ...
    ...
    Booleanを返すコード
    

    最後はBoolean型を返すため、a == bといった、テストの真偽を判定するコードを書く形式になります。

  4. ヘルパー関数として、booleanという関数を用意

    本書のサンプルに従って、ヘルパー関数を用意しました。引数として渡された変数の値に拠らず、必ずtrueを返します...結果としてテストは必ず成功します

    なお、先ほどのコードでは、本書に出てきたあらゆる型のデータを任意に生成してくれるany()のような便利ジェネレータがScalaCheckには無かったので、任意のAnyVal型のデータを生成するようにしていますが、本質的な意味は変わりません。

  5. ジェネレータは省略可

    ScalaCheckはジェネレータは省略可となっています。続くコードブロックの引数の型から型推論可能な場合は、自動的に型に合わせたジェネレータが選ばれます。そのため、先ほどのコード例は、以下のように書き換えることができます。

      property("always works") = forAll: (a: AnyVal) =>
     boolean(a)
    

デフォルトでは100回のテストが実行され、このコードサンプルでは全てのテストが必ず成功します。


とりあえず、テストが通る環境までは作ることができました。