
2026/03/25 0:28
- 仮説(Hypothesis) - 対立(Antithesis) - 合成(Synthesis)
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
## Summary Hegel は、複数のプログラミング言語に Hypothesis レベルの品質をもたらし、バグ検出力を高めるために Antithesis と緊密に統合された、新しいプロパティベーステストライブラリ群です。自身のプロトコルを介して軽量クライアントライブラリとして Hypothesis をラップし、言語固有のジェネレーター、コンポジット、および自動縮小プロセス(Rust の例で `Fraction::from_str("0/0")` からパニックを検出)を提供します。最初の安定版は Rust を対象としており、次に Go がリリースされ、その後 C++、OCaml、および TypeScript が続きます。Hegel は現在デベロッパープレビュー段階であり、コミュニティによるテストとフィードバックを奨励しています。 主な特徴は以下の通りです。 - **プロパティベーステスト**:ジェネレーター、コンポジット、および Hypothesis に類似した縮小機能 - **AI エージェントサポート**:Claude スキルがプロパティベーステストを自動生成し、初期のテスト作成ハードルを軽減します - **現在の制限**:高い同時実行性や非決定的なテストに対しては対応が難しく、今後の作業では Python 依存を排除した Rust ベースサーバーと並列処理の改善を予定しています 将来の開発: - 現在の Python 依存を置き換える Rust ベースサーバーが導入され、同時実行性が向上します - Antithesis の Bombadil イニシアチブと統合し、Hegel のプロトコルを縮小およびファズラー統合に使用することで、分散テストインフラ全体でバグ検出力を拡大します Rust やその他のサポート言語で開発・運用している企業や開発者にとって、Hegel を採用することでプロパティテストが効率化され、バグ検出が増加し、分散環境全体で再現可能なテスト結果を提供できます。
本文
ブログ
こんにちは。私は Hypothesis を書きました。その後、11 月に Antithesis に参加し、すぐにもう一人のコア Hypothesis メンテナである Liam DeVoe とともに作業を開始しました。避けられない結果として合成が生まれたため、本日私たちは新しいプロパティベースのテストライブラリ群、Hegel を紹介します。
Hegel は Hypothesis で見られるプロパティベーステストの品質をすべての言語に持ち込み、Antithesis とシームレスに統合してバグ検出力を高めることを目指しています。本日、Rust 用 Hegel をリリースしました。これは多数のライブラリの最初の一つです。次の 1〜2 週間で Go 用 Hegel のリリースを予定し、C++、OCaml、TypeScript 向けに既にさまざまな段階で準備が進んでいるライブラリを数か月以内に公開する計画です。
以下は Rust 用 Hegel の例です。ご興味をそそるでしょう:
#[hegel::test(test_cases = 1000)] fn test_fraction_parse_robustness(tc: hegel::TestCase) { let s: String = tc.draw(generators::text()); let _ = Fraction::from_str(&s); // パニックを起こしてはならない }
これは
fraction クレートにあるバグを発見します。from_str("0/0") がエラー値を返す代わりにパニックします。
もしこれだけで販売トークが十分でしたら、Hegel をここで確認してください。そうでなければ、プロパティベーステスト―特に Hegel ―が非常に優れている理由と、それを使うべき理由についてもう少し説明しましょう。
プロパティベーステストとは?
上記の Rust 用 Hegel の例で見たように、プロパティベーステストは「完全な具体的テストケースを自分で提供する」代わりに、テストが通るべき値域をライブラリに指定してテストします。私たちの fraction 例では、一般的な主張として次のようになります:パーサは決してクラッシュしない;有効な結果またはエラー値のいずれかを返すだけである。
このプロパティベーステストは無限に多くのテストコピーと考えることができます。各テストでは
s を別々の文字列に置き換えます:
#[test] fn test_fraction_parse_robustness() { let s: String = "0/0"; let _ = Fraction::from_str(&s); // パニックを起こしてはならない }
プロパティベーステストライブラリの利点は、これらの文字列を自分で考える必要がないことです。「クラッシュしない」は最も退屈なプロパティかもしれませんが、実際には非常に有用です。Python から来たときに特に役立ちます(Python プログラムは決してクラッシュしないように書くのは驚くほど難しい)――Rust でも同様です。
次にもう少し興味深い一般的なプロパティの例を示します:
use hegel::generators::{self, Generator, integers, booleans}; use rust_decimal::Decimal; use std::str::FromStr; #[hegel::composite] fn decimal_gen(tc: hegel::TestCase) -> Decimal { let int_part = tc.draw(integers::<i64>()); let has_frac = tc.draw(booleans()); if has_frac { let frac_digits = tc .draw(integers::<u32>().min_value(1).max_value(28)); let frac_val = tc .draw(integers::<u64>() .max_value(10u64.saturating_pow(frac_digits.min(18)))); let s = format!("{}.{:0>width$}", int_part, frac_val, width = frac_digits as usize); Decimal::from_str(&s).unwrap_or(Decimal::from(int_part)) } else { Decimal::from(int_part) } } #[hegel::test(test_cases = 1000)] fn test_decimal_scientific_roundtrip(tc: hegel::TestCase) { let d = tc.draw(decimal_gen()); let sci = format!("{:e}", d); let parsed = Decimal::from_scientific(&sci) .expect(&format!("Failed to parse {:?} from {}", sci, d)); assert_eq!(d, parsed); }
ここでは Hegel の「ジェネレータを組み合わせる」機能を使って
Decimal 用のカスタムジェネレータを定義しました。その後、一般的なプロパティである「ラウンドトリップ」をテストしています ― 何らかの形式に値を書き込み、再度読み込むと同じ値が得られるべきです。これはほぼすべてのプロジェクトで最も頻繁にテストされる非自明なプロパティの一つです。今回の場合、rust_decimal は科学的表記に変換するときにゼロを正しく扱わないというバグを発見しました。
プロパティベーステストで発見されたバグの大まかな分類は次のとおりです:
- ゼロを忘れた。
- このデータ型は呪われていて、呪いに触れてしまった。
- 複雑な構造的不変条件でミスした。
Antithesis では主に第 3 種類に注目していますが、実際には最初の 2 種類を解消することからプロパティベーステストの価値が得られると考えています。このタイプのバグは非常に簡単に見つかるためです。
たとえば、Unicode の呪い(報告されたバグ)に触れた例:
use heck::ToTitleCase; #[hegel::test(test_cases = 1000)] fn test_title_case_idempotent(tc: hegel::TestCase) { let s: String = tc.draw(generators::text()); let once = s.to_title_case(); let twice = once.to_title_case(); assert_eq!(once, twice); }
これは「一度タイトルケースに変換すれば、もう変更の必要はない」という直感的なプロパティをテストします。残念ながら、
ß を描画すると失敗します。最初の to_title_case が "SS" にし、その後の呼び出しが "Ss" に変換するためです。
「複雑な構造的不変条件」の現実的な例は
im ライブラリで Hegel が発見した既知のバグです:
#[hegel::test(test_cases = 1000)] fn test_ordmap_get_prev(tc: hegel::TestCase) { // 大きいキーセットをテストするためにサイズを増やすトリック。 let n = tc.draw(generators::integers::<usize>().max_value(200)); let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers()).min_size(n)); let im_map: OrdMap<i32, i32> = keys.iter().map(|&k| (k, k)).collect(); let bt_map: BTreeMap<i32, i32> = keys.iter().map(|&k| (k, k)).collect(); let key = tc.draw(generators::integers::<i32>()); let im_prev = im_map.get_prev(&key).map(|(k, v)| (*k, *v)); let bt_prev = bt_map.range(..=key).next_back().map(|(&k, &v)| (k, v)); assert_eq!( im_prev, bt_prev, "get_prev({}) mismatch with {} keys", key, im_map.len() ); }
これはあるサイズを超えると
get_prev が誤った値を返すことを発見します。この種のテストは「モデルベーステスト」と呼ばれ、対象をテストしたい何かがあれば、同じ機能を持つ単純な実装(例:全てメモリに保持する)を「モデル」として作り、プロパティベーステストでモデルと現実が常に一致するか確認します。
プロパティベーステストは他にも多くの使い方があります。この投稿では特に効果的なテスト手法の一部を紹介しました。始める際には既存のテストをリファクタリングしてみるとよいでしょうが、こうしたテスト思考に切り替えることで上記のような例をどこでも見ることができるようになります。
Hypothesis とは?
Hypothesis は世界で最も広く使われているプロパティベーステストライブラリです。その人気は Python で書かれているために多くのユーザーが存在することが一因ですが、Python には既に他にもプロパティベーステストライブラリがありました。Hypothesis が初めて広範囲に採用された理由は、以下のような利点を持っているからです。
主なメリット:
- 高品質ジェネレータと柔軟な構築ツール の豊富なライブラリ。
- 内部収縮(shrinking) が非常に優れており、常に読みやすい最小例を返します。他のライブラリで起こる「無効テストケースが生成される」「手動で shrinker を書く必要がある」「収縮品質が低い」などの落とし穴を回避。
- テストデータベース があり、失敗したテストを再実行すると同じ場所で高速に失敗します。
Hypothesis のユーモアは、他のすべてのプロパティベーステストライブラリが QuickCheck(Haskell 用)に基づいているという点です。QuickCheck は素晴らしい革新でしたが、基本的には Haskell プログラマ向けであり、正確性を求めるために多くの苦労を受け入れています。一方、Python 開発者は「正確さを得るために苦労しても構わない」という考えを持たずに Python を書いている場合がほとんどです。
Hypothesis は最初は QuickCheck のポートでしたが、時とともに独自の方向へ進化し、コードに対する「定理を書き込む」よりも「使いやすいテスト拡張」に重点を置くようになりました。この変革は Hypothesis の基盤であるモデルから派生しています。実際には、Hypothesis を作るために私(Liam と Zac Hatfield‑Dodds)たちは膨大な労力を投入しました。その結果、他のライブラリが同じレベルの競争優位性を持つことはほとんどありません。Go の Rapid ライブラリは最も信頼できるポートかもしれませんが、多くの「Hypothesis‑inspired」ライブラリはコアモデルを採用していないため、その恩恵を受けていません。
正直に言うと、新しい言語向けに同じ量の労力を投入するつもりはありません!すべての言語に Hypothesis‑レベルのプロパティベーステストライブラリが欲しいとは思っていますが、毎回それをメンテナンスし続けることは現実的ではありません。
少しクレイジーなアイデア
Antithesis に参加した際に私はこう提案しました:「Hypothesis をすべての言語で書く代わりに、他の言語が Hypothesis を簡単に利用できるようにする」と。Python バインディングで他言語ライブラリをラップすることは非常に一般的ですので、逆に行ってみませんか?
Hegel の核となるアイデアは次のとおりです:Hypothesis を実行し、その生成データ全てをソースとし、薄いクライアントライブラリでそれらを好みのターゲット言語の値に変換します。Hypothesis がすべて揃っているので、機能セットを完全に実装できます。
つまり、新しい Hegel ライブラリを立ち上げるたびに、Hegel プロトコルを実装し、ターゲット言語の適切な API を決定するだけで、選択した言語向けの高品質プロパティベーステストライブラリが完成します。実際に難しい部分は「ネイティブ感」を保つための細心の注意とセンスです。
Hypothesis‑レベルのプロパティベーステストをすべての言語で提供することに加えて、Antithesis も同時進行します。長期的には Hegel が Antithesis を実行する主要な入口点になる予定です。こうすれば、Hegel テストを書いて自前のインフラでスムーズに動かしつつ、Antithesis 上で実行してバグ検出力を高めたり、デバッグ・再現性の利点を享受できます。
短期的にはこの計画はほぼ機能しています!Hegel は高度に並列化された分散システムのテストにはまだ十分ではありません――これは Hypothesis の限界を継承した結果です。Antithesis を使えば、Hegel テストを書き、より強力な検出力が必要なら Antithesis 上で実行できます。今後数か月でさらにアップデートを予定していますので、ご期待ください。
なぜ Hegel を使うべきなのか?
私自身はバイアスがありますが、Hegel は将来のソフトウェア開発において大きな役割を果たすと確信しています。現在 AI ベースのワークフローで全てが変わる中でも、その価値は増します。
Liam が最近述べたように、プロパティベーステストは AI エージェント駆動開発が失敗しないために不可欠です。私たちが過去数十年にわたり人間主導のソフトウェア開発で成功させてきた多くの利点が、今こそさらに重要になっています。
私は AI 評価に関する作業を行っており、AI がコード評価に合格した後、プロパティベーステストを追加すると、多くの場合そのソリューションが失敗することに気付きました(これは人間が初めてコードを書いてテストを加えたときも同様です)。AI は以前よりずっと上手になりましたが、そのコードは依然として「だらしない」ため、補完ツールが必要です。
逆に言えば、プロパティベーステストへの入門障壁はこれまでのどんな時代よりも低くなっています。エージェントが実際にテストを書きやすいからです!私は告白します:Hegel で見つけたバグ例のほとんどを自分ではなく Claude が書いたものです。
コア Hegel ライブラリに加えて、プロパティベーステストを書くエージェント向けスキルも公開しています。完全に置き換えるわけではありませんが、人間が最初のテストを書くことは「データ生成について深く考える」必要があります。エージェントにその初期段階を乗り越えてもらうことは大きな勝利です。
もちろん、プロパティベーステスト自体を使うべきという主張と同じで、Hegel を特に選ぶ理由もあります。もしすでに満足できる高品質のプロパティベーステストがあるなら、Hegel はまだ初期段階です ― 最高のライブラリになることを目指していますが、完全ではありません。しかし試してみてください。Claude が既存テストを Hegel に一括変換するはずですので、自分で比較できます(もし既存テストに対して不満があればぜひ教えてください!)。
初めてプロパティベーステストを始めるなら、Hegel は最高の出発点です。Hypothesis の強力なコアを継承し、できるだけ使いやすくしました。
今後の展開
短期的に最も注力しているのは他言語向け Hegel です。Go、C++、OCaml、TypeScript はさまざまな準備段階にあります。今後数週間でこれらを公開する予定です。
ユーザーサポート、機能リクエスト、バグ報告なども増えると予想されますが、以下のような野心的計画も進行中です:
- Python 依存を除去。現在 Hegel テスト実行時のパフォーマンス制限になっています。長期計画として Rust で二つ目の Hegel サーバーを実装する予定ですが、いつ実現するかは未定です。
- Antithesis が得意とするワークロードに対して Hegel を強化。現在 Hypothesis が優れている従来型プロパティベーステストには適していますが、高度な並列・非決定性テストへも拡張したいです。
- Antithesis で使用される分散システムのテストをより良くする。これは Bombadil(Antithesis の新しいオープンソースプロパティベースイニシアチブ)との統合に向けた前提条件です。最初から Bombadil が Hegel プロトコルで優れた収縮と Antithesis フューザー統合を得る予定でしたが、Oskar がプロジェクトを開始した当時はそのようなランナーがありませんでした。ギャップを埋める方法を検討しています。
ディアレクティックに参加しよう
現在 Hegel は「開発者プレビュー」状態です。Hypothesis が非常に堅牢であるため、基盤のロジックはほぼ安定していると期待していますが、まだいくつか粗さがあります。API は満足できるものですが、完全ではありません。
ぜひ Hegel を試し、発見したバグ(自分のコードでも私たちのコードでも)を教えてください!