Hypothesisについて(研究者向け)

 
0
このエントリーをはてなブックマークに追加
Daichi Takayama
Daichi Takayama (高山 大地)

こちらの文章は、以下の記事を翻訳したものです。

元記事


私は、Hypothesisに関連する作業を自分の博士論文の一部にしようと考えています。しかし、研究者がHypothesisに注目すべき理由をまとめた自己完結型の記事が存在しないことに気づきました。

そこで、Hypothesisの意義と内容を初心者にも理解しやすいようにゼロから紹介することを目的として、この記事を書きました。この記事は特に博士課程の学生を指導する可能性のある教員を対象としていますが、それ以外の、この分野で働く多くの方々にとっても興味深い内容であると考えています。


研究の観点から見たHypothesisの重要性

簡潔に言うと、Hypothesisは、実践で非常に効果的であることが証明された既存のテスト手法(プロパティベースドテスト)を採用し、それをより幅広い層に向けてアクセスしやすくしています。これは、テストと検証に関する既存の研究文献からのアイデアを取り入れ、新しい実装を生み出すことで実現されました。これらの実装は実践での効果が証明されています。

詳しくは、以下のセクションに分かれた記事を参照してください:

  1. Hypothesisとは何か:Hypothesisの基本からの紹介。既にプロパティベースドテスト(例:QuickCheck)に精通している方は、このセクションはスキップしても構いません。
  2. Hypothesisの革新性とは:Hypothesisの技術的な水準とその興味深さについて。既に「Hypothesisの仕組み」を読んでいる場合、新たな情報はないためスキップ可能です。
  3. どのような先行研究に基づいているか:Hypothesisのインスピレーションとなった文献の紹介。興味深いリンクが含まれているため、スキップしない方が良いです。
  4. 興味深い研究方向性:Hypothesisの将来の方向性についての探求。これらの方向性のいくつかは、筆者が取り組むHypothesis関連の博士課程の一部になることを望んでいます。興味がある場合はスキップすべきではありません。
  5. この情報をどう扱うべきか?:記事の締めくくりとして、情報の取り扱い方について説明します。

これらのセクションを通じて、Hypothesisの重要性を研究者向けに伝え、その応用の可能性を深く掘り下げていきます。


Hypothesisとは何か

Hypothesisとは、プロパティベースドテストの一つの実装であり、元はHaskellのライブラリであるQuickCheckに由来しています。

プロパティベースドテストは、ユニットテストにランダムなデータを組み合わせた方法で、テストケースのエッジケースを探索し、自動的にエラーを見つけることを目指しています。この手法に関するより詳細な議論も過去に行われています。

以下は、Hypothesisを使用したプロパティベースドテストの例です。

from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_sort_is_idempotent(ls):
    sort1 = sorted(ls)
    assert sorted(sort1) == sort1

このコードは、py.testなどの標準的なテストランナーで扱うことができる通常の関数を定義しています。また、直接実行することも可能です。

if __name__ == "__main__":
    test_sort_is_idempotent()

テスト実行時、Hypothesisはランダムな整数のリストを生成し、そのリストをテスト関数に渡します。テストでは、リストをソートし、再度ソートし、その結果が同じであることを確認します。

Hypothesisが生成する全ての入力でテストが合格の場合、これは通常のテストと同じように見えます。しかし、テストが失敗した場合、Hypothesisはより単純な例でテストを繰り返し実行し、失敗の原因となる最小の入力例を見つけようとします。

例えば、以下のように不完全なsorted関数を実装した場合を考えてみましょう:

def sorted(ls):
    return list(reversed(ls))

この実装でテストを実行すると、次のような出力が得られます:

      @given(st.lists(st.integers()))
      def test_sort_is_idempotent(ls):
        sort1 = sorted(ls)
  >     assert sorted(sort1) == sort1
  E     assert [0, 1] == [1, 0]
  E       At index 0 diff: 0 != 1
  E       Use -v to get the full diff

  sorting.py:12: AssertionError

  ---- Hypothesis ----

  Falsifying example: test_sort_is_idempotent(ls=[0, 1])

Hypothesisはもともとより複雑な例からテストを始めたのかもしれませんが、最も単純な例(つまり2つの異なる要素を持つリスト)まで簡素化することに成功しました。

重要な点は、テストが再実行される際、Hypothesisは最後に見つけた失敗例から開始し、ゼロから新しいものを生成して縮小するわけではないということです。この特定のケースでは、例が非常に迅速に見つかり、常に同じものが見つかるため、それほど重要ではありません。しかし、より複雑で実行が遅いテストでは、これが開発ワークフローにおいて非常に重要になります。この特徴により、テストの実行が速くなり、バグが実際に修正されるまで失敗し続けることはありません。

テスト中にさらにデータを取り出すことも可能です:

@given(st.lists(st.integers(), min_size=1), st.data())
def test_sort_is_idempotent(ls, data):
    ls.sort()
    i = data.draw(st.integers(0, len(ls) - 1))
    assert ls[i - 1] <= ls[i]

このテストは、iがゼロになる可能性と、Pythonのリストで負のインデックスが許容される点を考慮していないため、失敗します:

      @given(st.lists(st.integers(), min_size=1), st.data())
      def test_sort_is_idempotent(ls, data):
          ls.sort()
          i = data.draw(st.integers(0, len(ls) - 1))
  >       assert ls[i - 1] <= ls[i]
  E       assert 1 <= 0

  sorting.py:15: AssertionError

  ---- Hypothesis ----

  Falsifying example: test_sort_is_idempotent(ls=[0, 1], data=data(...))
  Draw 1: 0

この方法で取り出されたデータに対しても、Hypothesisは単純化と例の保存を通常通り行います。

さらに、Hypothesisにはモデルベースドテストという形式もあります。これは、APIに対する一連の有効な操作を指定し、それらを用いてプログラム全体の動作を生成し、壊れやすい単純なプログラムを見つける試みです。モデルベースドテストは、プログラムの動作をより広範囲にわたって検証するのに役立ちます。


Hypothesisの革新性とは

エンドユーザーの観点から見ると、Hypothesisは以下のような重要な要素を加えています:

  • 広範な採用と実用性:Hypothesisは広く使われており、特に関数型プログラミングコミュニティ以外でのプロパティベースドテストの実装が成功していなかった点を考慮すると、これは重要です。Hypothesisの成功は、その革新的な実装と、「通常のテストのように感じる」デザインの決定によるものです。
  • データジェネレーターの簡素化:Hypothesisは、伝統的なQuickCheckメソッドよりもデータジェネレーターの指定を簡単にし、多くの機能を「無料で」提供します。これはClojureのtest.checkやErlangのQuickCheckに似ていますが、Hypothesisのデザインにより柔軟性が向上しています。
  • 例の保存と再生:任意の例を保存して再生できることは、開発ワークフローにおいて大きな改善をもたらします。他のプロパティベースドテストの実装では、これがないか、シードのみを保存するか、生成されたオブジェクトをシリアライズすることに依存していますが、Hypothesisはこれを効果的に行います。
  • テスト内での追加データ生成:テスト中に追加データを生成できる機能は、Hypothesisに特有で非常に有用です。

これらの要素は、プロパティベースドテストを一般に普及させ、Pythonコミュニティ内でのHypothesisの使用を広げ、ツールやライブラリの開発、PythonとPyPyの主要な実装での積極的な使用に寄与しています。

実装の観点から見ると、Hypothesisは以下のような新しい特徴を持っています:

  • Conjectureコアエンジン:これは、軽度に構造化されたバイトストリームに対する対話型ファジングツールとして考えることができます。
  • ストラテジーライブラリ:Conjectureの出力を取り、任意の値に変換するために設計されています。
  • 外部テストランナーへのインターフェース:Pythonでは、主にテストランナーで扱うことができる関数を公開します。Java Prototypeでは、JUnit特有のいくつかの興味深い機能とのやり取りが必要です。

Conjectureは、生成、縮小、シリアライゼーションを含む、Hypothesisのほとんどの機能をサポートしており、戦略の実装はこれらの特徴の認識を必要とせず、単にConjectureエンジンにバイトのブロックを繰り返し求めることで望ましい結果を返します。

これらの特徴について詳しく知りたい方は、先程ご紹介した「How Hypothesis Works」という記事を参照すると良いでしょう。この記事ではConjectureについて、そしてHypothesisがどのように構築されているかについての詳細が語られています。


どのような先行研究に基づいているか

Hypothesisの開発過程で、以下の主要な論文が基盤となっています:

  1. QuickCheck: a lightweight tool for random testing of Haskell programs: これはプロパティベースドテストの分野を事実上始動させた論文です。HypothesisはQuickCheckの実装としてスタートし、ユーザー向けのAPIはQuickCheckに大きく基づいていますが、実装面では大きな違いがあります。
  2. EXPLODE: a lightweight, general system for finding serious storage system errors: Conjectureエンジンの基本的なアイデアはこの論文から来ています。静的なデータ生成とは別にテストからデータを動的に描画できるインタラクティブなプリミティブを提供するというものです。

また、Hypothesisの開発に影響を与えた他の重要な研究やツールは以下の通りです:

  • American Fuzzy Lop: これはセキュリティ指向のファジングツールで、Hypothesisのファジングデザインに多くの影響を与えました。最も重要な革新の一つである分岐カバレッジメトリックは現在Hypothesisで使用されていませんが、この概念をHypothesis上で実装するプロトタイプが成功裏に作成されました。
  • Swarm Testing: Hypothesisの初期のデータ生成デザインに多大な影響を与えました。現在はConjectureの実装に直接存在していないものの、Conjectureがデータに意図的な相関を導入するために行ういくつかの手法は、こちらに触発されています。

これらの研究やツールは、Hypothesisの設計と実装において重要な役割を果たしており、プロパティベースドテストの分野におけるHypothesisの独自性と革新性に貢献しています。


Hypothesisに関する興味深い研究方向性

これから取り組む可能性のある研究方向性は多く存在しています。

しかし、必ずしも博士課程の研究テーマとなるとは限りません。博士課程では、これらのいくつか、または全てを含むより具体的な研究問題に焦点を当てることがほぼ確実です。指導教員となる方との議論を通じてより詳細に調整されることが期待されます。単に、私が興味を持ち、探求したいと思っている領域に過ぎません。

また、これらの研究方向性の多くは、Hypothesisの公開インターフェースを変更せずにHypothesisを改善することを目的としています。これは、既にHypothesisを使用している比較的大きな、そして成長し続けているオープンソースプロジェクトの存在もあるため、研究において大きな実用的な利点をもたらします。これらの変更の多くは、少なくとも部分的には、既存のテストを実行して新しい興味深いバグが見つかるかどうかを確認することにより検証することができます。

以下、私の考える、いくつかの最も興味深い研究テーマです。


より構造化されたバイトストリーム

現在、Hypothesisにおける私の直接的な研究焦点は、コアのConjectureプリミティブを、EXPLODEの原理により近い、より構造化されたものに置き換えることです。これは、Hypothesisユーザーが経験しているいくつかの実用的な問題、特にパフォーマンス関連の問題に対処することを目的としています。また、コアエンジンの上に構築できる新しい抽象化も可能にします。

このアイデアは、Conjectureへの呼び出しインターフェースを単純化し、可能な有効なバイトの範囲を指定して単一のバイトを取り出すことです。これによりConjectureはより詳細な情報を扱うことができ、新しい機能や抽象化を構築する基盤を提供します。

このプリミティブから、適切に縮小する任意の重み付けされたサンプラー(Alias Methodの変種を使用する可能性がある)や任意の文法(Boltzmann Samplersまたは類似のものを使用する可能性が高い)を再構築することができます。

これは、現在のやや場当たり的なバイトストリームの指定方法に比べて、はるかに徹底した高品質なデータ生成の基盤を提供します。

これは研究よりも工学的な側面が強いかもしれませんが、少なくともHypothesisのコアアプローチに関する私の論文をより説得力のあるものにするでしょうし、理論を興味深い方法で応用することにも繋がります。


グラスボックステスト

現在、Conjectureは実行されるテストをブラックボックスとして扱っており、テストの内容に関する情報がほとんど得られていません。

一つの明らかな方向性としては、American Fuzzy Lopから着想を得て、より多くのカバレッジ情報を取り入れることが考えられます。しかし、これまでのところ、このアプローチを実用的に適用する試みはあまり成功していません。主な問題は、見つけた技術が長時間のテスト実行を前提としているのに対し、Hypothesisは短時間でのテスト実行を前提としているため、これらの方法の効果が限定的であり、現状では優先度が低いということです。

しかし、理論上は、この限界があったとしても、非常に有益な方法である可能性があり、私はこれをさらに追求したいと思っています。

主な考え方は、検索を導くために使用可能な「タグ」という概念をConjectureのコアエンジンに組み込むことです。カバレッジ情報はタグの一つの源泉ですが、他にも様々な情報源が考えられます。例えば、私の以前の研究であるSchroedintegerは、コンコリックテスティングの形式の軽量版を実装しており、これもまた有用な情報源になり得ます。

このアプローチがどれ程オリジナルの研究であるか、または既存の研究の応用であるかはまだ未定ですが、この種の情報を限られた時間内でどのように活用するかを見極めることは、非常に興味深い結果をもたらす可能性が高いと考えています。コンコリックテスティングが実際にどのように機能するかを見ることで、さらに多くの疑問が生じるかもしれません。


Conjectureエンジンをもっと賢く

過去に検討したことのある一つのテーマは、文法推論を利用して縮小とデータ生成を改善することです。

当時直面した障害は、私が使用していたアルゴリズム - L*検索の最適化された変種 - が、私が試した問題で実際には良いパフォーマンスを発揮しなかったことでした。

Synthesizing Program Input Grammarsは、実用的なシナリオでずっと優れた文法推論を提供することを約束しており、これはこの問題領域に非常に密接に関連しているので、この問題を再訪してその有用性を確かめたいと思っています。

Conjectureエンジンがテスト対象のシステムの状態を探査し、特にグラスボックステストの機能と組み合わせた場合に、興味深い潜在的な振る舞いを特定する他の方法も多くありそうです。

ここには多くの興味深い研究方向があると思います - 特にこれをグラスボックステストと組み合わせた場合ですが、過去にこれをうまく機能させることはできなかったので、最初の一歩はそれを実現することです!

また、実際にバグを見つけるのに何がうまくいくか、何がうまくいかないかを見るために、多くの実用的な実験が必要になるでしょう。この分野では特に、Hypothesisでテストされたオープンソースプロジェクトのコーパスが非常に役立つでしょう。


その他のテスト抽象化

Hypothesisは主にプロパティベースドテストのためのライブラリとして知られていますが、そのコアであるConjectureエンジンは、プロパティベースドテストとは直接関連がない、より強力な低レベルのテスト抽象化を提供しています。これがどこまで進められるかを探ることは興味深いでしょう。既存のステートフルやモデルベースドテストはその方向への一歩ですが、それ以外にも直接応用される可能性があります。例えば、上述のいくつかの機能と組み合わせて、バイナリの低レベルファジングやスレッドスケジューリングを駆動するのに使用することができます。

Conjectureの分離のメリットは、非常に自己完結しているため、他のツールを再構築する際の主なビルディングブロックとして使用できるでしょう。これにより、その主要な機能の多くをタダで得ることができます。

現時点では、この方向での具体的な計画はありませんが、テスト文献のさらなるレビューを行った後に、ここに興味深い可能性がいくつか出現することが予想されます。

この分野は、特に目を引くような応用が見つかるまでは主に工学的な側面が強いかもしれませんが、基本的な技術の潜在的な可能性は、そのような応用に対する良い可能性を提供することだと言えるでしょう。


この情報の取り扱い方について

この情報の取り扱い方は、あなたがどのような立場にあるかによって異なります。

  • もし既に私と話し合いをしている場合、それはあなたが博士課程の指導教員となる可能性があるということです。この記事の中で何があなたの興味を引くか教えてください。そして、どんな質問でもお気軽にどうぞ。
  • 博士課程の指導教員となる可能性のある方で、まだ話していないけれどぜひ話しがしてみたいと思っている方は、ぜひ連絡してください!
  • それ以外の方であれば、扱い方はあなた次第です。論文や質問など、何でも自由に送っていただければと思います。
info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。