クイックガイド
この記事は、公式ガイドから引用した 仮説-クイックスタートガイドページの翻訳です 。
2017年11月23日にアレクサンダー・ショーリンが「モスクワパイソンミートアップ50」で発表したことを除いて、ロシア語で仮説の使用に関する有用な情報を見つけることができませんでした。 私はそれを理解することにしました。 その結果、私は何かを翻訳しました。 だから、私は共有することにしました。
このドキュメントでは、仮説を開始するために必要なすべてについて説明する必要があります。
例
ランレングスエンコーディングシステムを作成し、それができることを確認したいとします。
Rosetta Code wikiから直接取得した次のコードがあります (OK、コメント付きのコードをいくつか削除し、フォーマットを修正しましたが、関数は変更しませんでした)。
def encode(input_string): count = 1 prev = '' lst = [] for character in input_string: if character != prev: if prev: entry = (prev, count) lst.append(entry) count = 1 prev = character else: count += 1 else: entry = (character, count) lst.append(entry) return lst def decode(lst): q = '' for character, count in lst: q += character * count return q
職責からの不変条件をチェックするこの機能のペアのテストを作成します。
この種のencoding/decoding
がある場合の不変式は、何かをエンコードしてからデコードすると、同じ値が返されることです。
仮説でこれをどのように行うことができるか見てみましょう:
from hypothesis import given from hypothesis.strategies import text @given(text()) def test_decode_inverts_encode(s): assert decode(encode(s)) == s
(この例では、 pytestでテストを検出して実行します。後で実行できる他の方法について説明します)。
テキスト関数は、仮説が検索戦略と呼ぶものを返します。 ある種の値を生成および単純化する方法を記述するメソッドを持つオブジェクト。 次に、 @given
デコレータは関数テストを受け取り、それをパラメーター化されたものに変換します。呼び出されると、このストラテジーからの広範な一致データに対してテスト関数を実行します。
いずれにせよ、このテストはコードのエラーをすぐに見つけます:
Falsifying example: test_decode_inverts_encode(s='') UnboundLocalError: local variable 'character' referenced before assignment
注 :ローカル変数character
は、割り当ての前に記載されています。
空の文字列に対して呼び出された場合、このコードは単純に正しくないという仮説が正しく指摘されています。
関数の先頭に次のコードを追加するだけでこれを修正すると、仮説はコードが正しいことを知らせます(テストに合格するため、何もしません)。
if not input_string: return []
この例を常に確認したい場合は、明示的に追加できます。
from hypothesis import given, example from hypothesis.strategies import text @given(text()) @example('') def test_decode_inverts_encode(s): assert decode(encode(s)) == s
これを行う必要はありませんが、わかりやすく、例の信頼性の高い検索を行うために役立ちます。 また、ローカルの「トレーニング」の一環として、どのような場合でも、仮説は例を覚えて再利用しますが、現在、継続的インテグレーション(CI)システムでデータを交換するための許容できる優れたワークフローはありません。
また、 example
の引数とgiven
キーワードは、名前と位置の両方を指定できることに注意してください。 次のコードも同様に機能します。
@given(s=text()) @example(s='') def test_decode_inverts_encode(s): assert decode(encode(s)) == s
より興味深いエラーがあり、カウンターをリセットするのを忘れたと仮定します
ループ内。 encode
メソッドで1行スキップしたとします。
def encode(input_string): count = 1 prev = '' lst = [] for character in input_string: if character != prev: if prev: entry = (prev, count) lst.append(entry) # count = 1 # prev = character else: count += 1 else: entry = (character, count) lst.append(entry) return lst
次の例では、仮説がすぐにわかります。
Falsifying example: test_decode_inverts_encode(s='001')
提示された例は本当に非常に単純であることに注意してください。 仮説は簡単ではありません
テストで使用可能なサンプルを見つけ、サンプルを単純化する方法を知っている
彼は小さくて理解しやすいと考えています。 この場合、2つの同一の
値は、カウンターを1以外の数に設定するのに十分です。
スコアをリセットする必要がある別の値ですが、この場合
しませんでした。
仮説の例は、実行可能な有効なPythonコードです。 関数を呼び出すときに明示的に指定する引数は仮説によって生成されません。すべての引数を明示的に指定すると、仮説は基本関数を1回呼び出すだけで、何度も実行されません。
設置
仮説はavailable on pypi as "hypothesis"
。 以下でインストールできます:
pip install hypothesis
ソースコードから直接インストールする場合(たとえば、
変更を行い、変更されたバージョンをインストールします)これを行うには:
pip install -e .
最初にテストを実行して、破損していないことを確認する必要があります。 次のようにできます:
python setup.py test
それらがまだインストールされていない場合は、テストの依存関係をインストールしようとすることに注意してください。
これらはすべてvirtualenvで実行できます。
たとえば、次のように:
virtualenv venv source venv/bin/activate pip install hypothesis
インストールされたシステムパッケージに影響を与えずに仮説を試すための分離環境を作成します。
テスト実行
上記の例では、 pytestにテストを検出して実行させていますが、自分で明示的に実行することもできます。
if __name__ == '__main__': test_decode_inverts_encode()
または、 unittest.TestCase :
import unittest class TestEncoding(unittest.TestCase): @given(text()) def test_decode_inverts_encode(self, s): self.assertEqual(decode(encode(s)), s) if __name__ == '__main__': unittest.main()
注:これは、仮説が提供するように指示されていない引数を無視するために機能します(位置引数は右から始まります)。したがって、テストのself
引数は単に無視され、通常どおりに機能します。 また、仮説がテストをパラメーター化する他の方法とうまく機能することを意味します。 たとえば、いくつかの引数にpytestフィクスチャを使用し、他の引数にHypothesisを使用すると、 うまく機能します。
テストを書く
仮説のテストは2つの部分で構成されます。選択されたテスト構造では通常のテストのように見えますが、いくつかの引数が追加された関数と、これらの引数の提供方法を示す@given
デコレーターです。
これを使用する方法の他の例を次に示します。
from hypothesis import given import hypothesis.strategies as st @given(st.integers(), st.integers()) def test_ints_are_commutative(x, y): assert x + y == y + x @given(x=st.integers(), y=st.integers()) def test_ints_cancel(x, y): assert (x + y) - y == x @given(st.lists(st.integers())) def test_reversing_twice_gives_same_list(xs): # ( 0 # 100 ), . ys = list(xs) ys.reverse() ys.reverse() assert xs == ys @given(st.tuples(st.booleans(), st.text())) def test_look_tuples_work_too(t): # , , # . assert len(t) == 2 assert isinstance(t[0], bool) assert isinstance(t[1], str)
上記の例で見たように、 @given
引数を位置または名前付きとして渡すことができます。
どこから始めるか
これで、仮説を使用してコードのある種のテストを作成するための基本について十分に理解できました。 学ぶための最良の方法はそれを行うことですので、試してみてください。
この種のテストをコードに使用する方法についてのアイデアが少し難しい場合は、いくつかのヒントがあります。
- 対応するランダムデータを使用して関数を呼び出して、それらの関数でエラーを取得するだけです。 これがどのくらいの頻度で機能するかに驚くかもしれません。 たとえば、コーディング例で最初に見つかったエラーは、競合にさえ達していませんでした。指定したデータを処理できず、何かが間違っていたためではなく、クラッシュしました。
- テストで重複を探します。 いくつかの異なる例で同じことをテストしているケースはありますか? 仮説を使用した1つのテストでこれを要約できますか?
- この部分はF#で実装されることを意図していますが 、仮説を使用するための良いアイデアを見つけるのを助けるのにまだ非常に便利です。
起動に問題がある場合は、お気軽にお問い合わせください。