はしくれエンジニアもどきのメモ

情報系技術・哲学・デザインなどの勉強メモ・備忘録です。

Bing Chatとペアプロできる時代がきた

Bing Chatとペアプロできる時代がきた

世間はChat-GPT4で持ち切りですが、Bing Chatでもいい感じのコードを生成してくれるぞという成功例メモ。 今のBingChatは最大15ターン150回まで利用できるようになった。 個人的には、

  • 無料
  • APIキー必要なし
  • モバイル利用OKは

かなりのメリットだと思っている。

今回は、doctestを使ったときにmodule単位でなく、 doctest.run_docstring_examples()あたりを使ってクラス単位でテストして、(失敗数, try数)である集計結果を得る方法はないのか聞いてみた。 (なぜかdoctest.run_docstring_examples()TestResultを返り値で返してくれない。)

一応、事前のやり取りで、doctest.DocTestRunner`を使うと良さそうというアタリは付けている。

で、(私が少し手直ししているが)できたコードはこんな感じである。🎉

import doctest
def doctest_objAll(obj, verbose = True, optionflags = 0):
    finder = doctest.DocTestFinder()
    runner = doctest.DocTestRunner(verbose = verbose, optionflags = optionflags)
    tests = finder.find(obj)
    for test in tests:
        runner.run(test)
    return runner.summarize()

ちゃんとこのコードは動き、しかも公式ドキュメント(https://docs.python.org/ja/3/library/doctest.html)に そっくり載ってるコードでないところが重要ポイントである。(なぜドキュメントにサンプルコードないねんと言いたいところだが)

このコードはできるまでの流れを、(スマートではないが)以降に残しておく。

環境・事前準備

  • Windows11 64bit
    • Edge バージョン 110.0.1587.69 (公式ビルド) (64 ビット)
    • WSL2
    • Docker Desktop 4.17.0 (99724)
    • jupyter
      • python 3.10(生成コードの確認)

Bing Chat とのやりとり

[1]: 「pythonのdoctest_runnerを使ってモジュール単位でなくクラスとその中のメソッド単位でテストして失敗数を返り値で方法を教えて」

ちょっと誤字ってますがこの内容で聞いてみます。

Pythonのdoctestモジュールは、対話的なPythonセッションのように見えるテキストを探し出し、セッションの内容を実行して、そこに書かれている通りに振舞うかを調べます1。ただし、私が見つけた情報では、doctest_runnerを使ってクラスとその中のメソッド単位でテストして失敗数を返す方法については詳しく説明されていません。もう少し具体的な質問がありますか?

見つからないと言い出したので、誤字の影響も加味して正式名で使い方を聞いてみます。

[2]: 「doctestRunnerの使い方を教えて」

doctestRunnerは、DocTestのテストケースを実行し、統計を蓄積するために使用されるクラスです。runメソッドは、単一のDocTestケースを処理するために使用されます。試行したテストケースの数tと失敗したテストケースの数fのタプル(f, t)を返します1。ただし、私が見つけた情報では、doctestRunnerの詳しい使い方については説明されていません。もう少し具体的な質問がありますか?

検索して教えてくれました。ただ詳しい使い方は見つからなかったようです。 サンプルコードがあるか聞いてみます。

[3]: 「サンプルコードはありますか?」

はい、doctest.DocTestRunnerのサンプルコードがいくつかあります。例えば、Program Creekというサイトには、doctest.DocTestRunnerの30のコード例があります1。また、Pythonの公式ドキュメントにも、doctestモジュールを使用したテストの例があります2。

import doctest
doctest.testfile("example.txt")

上記の短いスクリプトは、ファイルexample.txtに含まれるインタラクティブPythonの例を実行して検証します。ファイルの内容は、単一の巨大なdocstringとして扱われます。ファイルにPythonプログラムを含める必要はありません!2

doctest.testfileのサンプルコードを教えてくれました。 ここで質問の選択肢「doctestRunnerでクラスとメソッドをテストする方法」

[4]: 「doctestRunnerでクラスとメソッドをテストする方法」

doctestRunnerを使用して、クラスとその中のメソッドをテストすることができます。テストを実行するには、ファイルの末尾に次のコードを追加します。

if __name__ == "__main__":
   import doctestモジュール単位でなくクラス単位でやりたいです。doctestrunnerとdoctest.run_docstring_examplesを組み合わせる方法ある?
   doctest.testmod()

テストがクラス全体を対象とする場合は、クラスのdocstringに配置します。コンストラクターをテストする場合は、コンストラクターのdocstringに配置します1。

doctestRunnerというより、doctest.testmod()の使い方を教えてくれました。 それはモジュール単位で行うのでそれじゃないと主張し、もう一歩踏み込んで「doctestrunnerとdoctest.run_docstring_examplesを組み合わせる方法あるか?」という質問をします。

はい、doctest.run_docstring_examples()を使用して、特定のクラスやメソッドのdocstring内のテストを実行することができます。例えば、次のように書くことができます。

import doctest
doctest.run_docstring_examples(MyClass.my_method, globals())

上記のコードは、MyClassクラスのmy_methodメソッド内のdocstringに記載されているテストを実行します。1

doctest.run_docstring_examplesのサンプルを出してきましたが、DocTestRunnerと組み合わせられるかの質問に答えてません。もう一度聞いてみます。

[6]:「そのときにdoctestrunnerの結果もえる方法ある?」

はい、doctest.DocTestRunnerオブジェクトを使用して、doctest.run_docstring_examples()の結果を取得することができます。例えば、次のように書くことができます。

import doctest
runner = doctest.DocTestRunner()
doctest.run_docstring_examples(MyClass.my_method, globals(), test_runner=runner)
print(runner.summarize())

上記のコードは、MyClassクラスのmy_methodメソッド内のdocstringに記載されているテストを実行し、その結果を表示します。

なんかそれっぽいコードが出てきましたが、これは嘘コードです。ドキュメントを見ればわかりますが、 doctest.run_docstring_examples()にはtest_runnerという引数は与えられません。 実は事前のやりとりでもこの嘘コードを教えられました。

このコードは間違っており、他にいい方法があるか聞いてみます。 すると・・

[7]: 「最新のドキュメント(https://docs.python.org/ja/3/library/doctest.html#doctest.DocTestRunner)ではdoctest.run_docstring_examplesはtest_runner引数を持ちません。他にいい方法ある?」

import doctest
finder = doctest.DocTestFinder()
runner = doctest.DocTestRunner()
tests = finder.find(MyClass.my_method)
for test in tests:
 runner.run(test)
print(runner.summarize())

doctest.DocTestFinder()を使った(本当に動くなら)良さげなコードを教えてくれました。 DocTestFinderがどんなものかわからないので聞いてみます。

[8]: 「これは良さそうです。DocTestFinderとは何ですか?」

doctest.DocTestFinderは、モジュール、クラス、メソッド、関数などのオブジェクトからdocstringを抽出し、~

doctest.DocTestFinderはdocstringの抽出してくれるようです。 ただ、先程のコードではクラス内関数を1つ指定して使って面倒そうだったので、動的にクラス内関数を与えられないか聞いてみます。

[9]: 「関数を指定するのは面倒なのでdocstringの書かれた関数を動的に取得する方法ある?」

finder = doctest.DocTestFinder()
tests = finder.find(my_module)

どうやらDocTestFinder().find(module)と与えればその中のdocstring全てを抽出してくれるようです。 クラスオブジェクトも与えられそうですが少し確認の意味も込めて、moduleの代わりにクラスにするとどうなるか聞いてみます。

[10]: 「モジュールでなくクラスだとどうなる?」

finder = doctest.DocTestFinder()
tests = finder.find(MyClass)

ほぼ完成しましたが、せっかくなのでまとめたコードを出してもらいましょう。

[11]: 「そこからdoctestrunnerで結果を受け取るにはどう書けば良い?」

import doctest
finder = doctest.DocTestFinder()
runner = doctest.DocTestRunner()
tests = finder.find(MyClass)
for test in tests:
 runner.run(test)
print(runner.summarize())

9割完成です。 せっかくなので関数化してもらいます。

[12]: 「おーこれは最高です。他でも使えるようにテスト関数化するとどうなる?」

import doctest

def run_doctest(obj):
   finder = doctest.DocTestFinder()
   runner = doctest.DocTestRunner()
   tests = finder.find(obj)
   for test in tests:
       runner.run(test)
   return runner.summarize()

https://sl.bing.net/gQJU6UONDwW

9割9分完成ですが、このまま動かすとテストの詳細表示をしてくれないのでverboseの設定をお願いします。

[13]: 「それで詳細結果 verboseを得る方法ある?」

runner = doctest.DocTestRunner(verbose=True)

先程の関数のDocTestRunnerにこのverboseを設定すれば完成です。 実際には、Exceptionやerrorもテストできるようにoptionflagsもできたほうがいいです。