AIの片隅で(52) Googleの生成AI、Gemini、関数のunittestを作って

Joseph Halfmoon

前回、Gemini様にPythonのスクリプトにpydoc形式のドキュメンテーション・コメントを挿入していただきました。「動作、よく分かっているみたいじゃん、だったらついでにユニットテストも書いてよ」ということで今回はプロンプトにお願い。時々ポカもあるものの、ほぼOKっす。お陰で年寄がバグ作っていたことも発覚。

AIの片隅で 投稿順index

※Google様の生成AI、Gemini(無料プランだけれども)を使ってプログラムを「吟味」してもらっています。

unittestとプロンプトへの入力

ユニットテストは、ソフトウエアの部品レベルの単体テストです。Pythonの場合、その名もズバリ unittest という名のモジュールがあるので、年寄はこれを利用させていただいております。

まあね、「テスト・ドリブン」というと先にテスト書いておいて、後から実際の機能をコーディングするみたいですが、忘却力の年寄は中途半端。多くの場合、関数の原型を書き、そのユニットテストを書き、テスト動かしてみて修正といったグズグズな感じで書いてます。今回のは、前回コメントを挿入した「動いている筈」かつ「とりあえずのユニットテストも含んでいる」ソースに対して、その中の関数を一つ取り出し、Gemini様にunittestの生成をお願いしてみようと。

プロンプトへの入力は以下です。

unittestモジュールを使って以下の関数をユニットテストする関数を作製して。

そして上記につづいて以下の関数をアップロードしてみました。なお関数のドキュメンテーションコメントは前回Gemini様に生成していただいたものです。

def freqIN(arg, dval):
  """Processes the frequency argument and converts it to Hz with unit prefix handling.

  Args:
    arg: The argument string containing the frequency value.
    dval: The default value to use if the argument is invalid.

  Returns:
    The frequency value in Hz.
  """

  prefixes = 'mkMG'
  vl, lastChar = chkVal(arg, dval)
  pfx = prefixes.find(lastChar)
  if pfx == 0:
    ex = 1e-3
  elif pfx == 1:
    ex = 1e3
  elif pfx == 2:
    ex = 1e6
  elif pfx == 3:
    ex = 1e9
  else:
    ex = 1
  return vl * ex
Gemini様のご回答とそれへの対処

何時ものようにGemini様には三案回答いただきました。まずは第1案の冒頭部分が以下に。A1

上記につづいてunittestのテスト関数も生成されております。こんな感じ。A1class

青のマーカは余計な部分、黄色は無効ではなく有効デス。どうも案1のテスト関数は「雰囲気でてるけど、コマケー誤解もありーの」という感じ。

実際、回答をファイルにして走らせてみると以下のエラーもあり。A1error

この原因は、テスト対象の関数 freqINの内部で呼び出している chkValという関数がソースに含まれていないためであります。Gemini様はさらっと「完結したっぽい」したソースを生成していただいてますが、ターゲットの関数がちゃんと走るかどうかは、オメーの責任じゃろ、ということか。これはGemini様でなく私の問題ね。

慌てて freqIN の内部で呼び出している関数とその先の関数をとりこみました。そこでようやく、先ほどの見えていたアサーションの「誤解」問題に到達。以下のごとし。A1error2

アサーション部分に関しては案2の方が「当方の意図にあっている」感じがしたので、案2の方に切り替えることにいたしました。案2のテストが以下です。A2class
案2も完璧ではなく、赤線引いた1000は不正ではなくOKな値です。100は正常な方のテストに入っているのに1000は不正とは首尾一貫しないGemini様です(よくあることか。)

案2の赤線抜きに差し替えて走らせてみたものの、またもやエラーが。A1error3

これは引数に空文字列与えたときのエラーみたい。これは実際のバグじゃないだろうか。。。

まあこの先はGemini様の問題ではなく、Gemini様に摘発された老人のコードのバグっす。テキトーにバグ修正を行い動作するようになった様子が以下に。TEST_OK

念のため、Gemini様生成のテスト関数と、それへの対処の手修正を反映したソースが以下に。

import unittest

def parse2float(st):
  """Parses a string to a float.

  Args:
    st: The string to be parsed.

  Returns:
    The parsed float value, or None if parsing fails.
  """

  try:
    work = float(st)
  except:
    return None
  return work


def chkVal(arg, dval):
  """Checks the validity of the argument and converts it to a float.

  Args:
    arg: The argument string to be checked.
    dval: The default value to use if the argument is invalid.

  Returns:
    A tuple containing the parsed float value and the last character of the argument string.
  """

  vl = parse2float(arg)
  if vl is None:
    nLen = len(arg)
    if nLen > 1:
      bdy = arg[0:nLen - 1]
      lastChar = arg[nLen - 1]
      vl = parse2float(bdy)
    else:
      lastChar = "0"
    if vl is None:
      vl = dval
  else:
    lastChar = "0"
  return (vl, lastChar)

def freqIN(arg, dval):
  """Processes the frequency argument and converts it to Hz with unit prefix handling.

  Args:
    arg: The argument string containing the frequency value.
    dval: The default value to use if the argument is invalid.

  Returns:
    The frequency value in Hz.
  """

  prefixes = 'mkMG0'
  vl, lastChar = chkVal(arg, dval)
  pfx = prefixes.find(lastChar)
  if pfx == 0:
    ex = 1e-3
  elif pfx == 1:
    ex = 1e3
  elif pfx == 2:
    ex = 1e6
  elif pfx == 3:
    ex = 1e9
  elif pfx == 4:
    ex = 1
  else:
    ex = 1
    vl = dval
  return vl * ex

class TestFreqIN(unittest.TestCase):
  def test_valid_input(self):
    # 正常な入力値に対するテスト
    # 100Hz
    self.assertEqual(freqIN("100", 0), 100.0)
    # 10kHz
    self.assertEqual(freqIN("10k", 0), 10000.0)
    # 1MHz
    self.assertEqual(freqIN("1M", 0), 1000000.0)
    # 1GHz
    self.assertEqual(freqIN("1G", 0), 1000000000.0)

  def test_invalid_input(self):
    # 不正な入力値に対するテスト
    # 文字列
    self.assertEqual(freqIN("abc", 0), 0.0)
    # 空白
    self.assertEqual(freqIN("", 0), 0.0)
    # 不明な単位
    self.assertEqual(freqIN("100x", 0), 0.0)

  def test_default_value(self):
    # デフォルト値のテスト
    self.assertEqual(freqIN("abc", 100), 100.0)
    self.assertEqual(freqIN("", 100), 100.0)

if __name__ == "__main__":
  unittest.main()

やあ、Gemini様のお陰でバグが直ったよ~。元のプログラムがダメダメだったか。。。

AIの片隅で(51) Googleの生成AI、Gemini、Pythonスクリプトにコメント へ戻る

AIの片隅で(53) Googleの生成AI、Gemini、ブロックダイアグラムを描いて へ進む