LoginSignup
21
3

More than 1 year has passed since last update.

ChatGPTで作られたElixirのプログラムをテスト駆動する

Last updated at Posted at 2022-12-07

ChatGPTでElixirのプログラムも出力できたので、遊んでみました

ChatGPTのサイトはこちら

ChatGPTとは

直接ChatGPTに答えてもらいました

『ChatGPTは、OpenAIが開発した大規模な言語モデルです。このモデルは、膨大なテキストデータセットから学習させることにより、自然言語の処理や対話生成などの機能を実現します。また、ChatGPTは高い精度を誇り、人間と似たような対話ができることで知られています。』

ChatGPT (1).png

せっかくなので、ちょっとだけ文章を入力して遊んでみました

ChatGPT (2).png

AIの答えは
『健康であることはとても大切です』

『Elixirを使えば何でも出来るわけではありません。プログラミング言語にはそれぞれの長所と短所があり、Elixirもそうです。そのため、 どのようなものを作りたいかによって、適切な言語を選択する必要があります。

まぁそうですよね

これまじすごい!20数年前に草の根BBSであった人口無能や、一太郎のおまけにつていたコンピュータが返事してくれるツールと比べ物にならないぐらいの技術進化!

こんな質問も

Screenshot from 2022-12-06 21-59-26.png
※月曜アキナゼミより

1、4を積極的にtryして、after心のもやもやリソースと傷を開放して滅びましょう
つまり、現時点ではAIの回答内容によってはファクトチェックを検討に検討を重ねる必要がありそうです

そんなわけで次は、AIにElixirのプログラムを作ってもらいましょう
そして、プログラムのコピペで何もしないと、炎上することはあります
その為、プログラムを理解して正しいか検証しましょう

ほげ.jpg
※検討使の選択とジャッパーンを戦いの舞台にするイメージ図

つまりこの記事は

・ChatGPTでElixirのソース出力するとどうなるの?
・関数テストをしてみよう

の2つの視点で書きます

お題

2つの物体の当たり判定する関数をelixirで実装して

ChatGPTが出力した結果
ChatGPT.png

CharGPTの結果

defmodule Rectangle do
  def collided?(x1, y1, w1, h1, x2, y2, w2, h2) do
    x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2
  end
end
Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5)

まずはiex起動して検証

文法的問題はなさそうで、動いているぽい(細かい検証は次で)
Screenshot from 2022-12-06 20-39-03.png

ではテストパターンを考えてみよう

ここではAとBの2つの正方形(又は長方形)の当たり判定を考えます

大きく分類して2つの結果が得られます

・2つの物体が重ならない状態
1.png

・2つの物体が重なる状態
2.png

更に細かく座標の位置関係を考えてみよう

・境界線で重なっているパターン
image.png

・境界線付近で重ならないパターン
3.png

サイズの違いによるパターン

4.png

テストプログラムを作ろう

プロジェクトの作成

$ mix new rectangle

実行結果

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/rectangle.ex
* creating test
* creating test/test_helper.exs
* creating test/rectangle_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd rectangle
    mix test

Run "mix help" for more commands.

rectangleプロジェクトに移動

$ cd rectangle

プロジェクトにテストの仕組みを組み込む

mix test.watchを使えるようにして、ファイル保存時に自動的にテストを行うようにします

参考

組み込んでみよう

mix.exsを編集

mix.exs
defmodule Rectangle.MixProject do
  use Mix.Project

  def project do
    [
      app: :rectangle,
      version: "0.1.0",
      elixir: "~> 1.13",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
+     {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
    ]
  end
end

mix test.watchの準備と実行

$ mix deps.get

実行結果

Resolving Hex dependencies...
Dependency resolution completed:
New:
  file_system 0.2.10
  mix_test_watch 1.1.0
* Getting mix_test_watch (Hex package)
* Getting file_system (Hex package)

testの自動実行

$ mix test.watch

test結果

Running tests...
Compiling 1 file (.ex)
Generated rectangle app
..

Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 doctest, 1 test, 0 failures

ファイルが保存する度に、自動的にテストする仕組みができました

検証対象の関数を作成

collided?関数とExamplesを追加
Examplesを追加するとDoctestが行われます

lib/rectangle.ex
defmodule Rectangle do
  @moduledoc """
  Documentation for `Rectangle`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Rectangle.hello()
      :world

  """
  def hello do
    :world
  end

+ @doc """
+ Collided detection.

+ ## Examples

+     iex> Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5)
+     true

+ """
+ def collided?(x1, y1, w1, h1, x2, y2, w2, h2) do
+   x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2
+ end
end

保存すると下記の結果が表示されます

Running tests...
...

Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 doctests, 1 test, 0 failures

Randomized with seed 779417

2doctestsに変化しました

テストモジュールの作成

defmodule RectangleTest do
  use ExUnit.Case
  doctest Rectangle

  test "greets the world" do
    assert Rectangle.hello() == :world
  end

+ test "collided" do
+   assert Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5) == true
+ end

+ test "not collided" do
+   assert Rectangle.collided?(1, 1, 10, 10, 15, 5, 5, 5) == false
+ end

end

※ assert Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5) で良いのですが以後の改造の為にわざと
assert Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5) == true で表現してます

保存後の結果

Running tests...
.....

Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 doctests, 3 tests, 0 failures

Randomized with seed 454614

3 testsに変化しました

テストをわざと誤りにしてみると

Randomized with seed 454614

Running tests...


  1) test collided (RectangleTest)
     test/rectangle_test.exs:9
     Assertion with == failed
     code:  assert Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5) == false
     left:  true
     right: false
     stacktrace:
       test/rectangle_test.exs:10: (test)

....

Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 doctests, 3 tests, 1 failure

1 failureと表示します

テストをパラメータ化しよう

test/rectangle_test.exs
defmodule RectangleTest do
  use ExUnit.Case
  doctest Rectangle

  test "greets the world" do
    assert Rectangle.hello() == :world
  end

- test "collided" do
-   assert Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5) == true
- end

- test "not collided" do
-  assert Rectangle.collided?(1, 1, 10, 10, 15, 5, 5, 5) == false
- end

+ test_params = [
+   [1, 1, 10, 10, 5, 5, 5, 5, true],
+   [1, 1, 10, 10, 15, 5, 5, 5, false]
+ ]

+ for test_param <- test_params do
+   @test_param test_param
+   test "collided #{inspect(@test_param)}" do
+     [x1, y1, w1, h1, x2, y2, w2, h2, ret] = @test_param
+     assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
+   end
+ end
end

実行結果

Running tests...
.....

Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 doctests, 3 tests, 0 failures

Randomized with seed 984685

テストパターンを増やそう

スプレッドシート等を使ってパターンを考えてみる

a.png

テストパターンからソースを起こす

手軽な方法はエディター等で
スプレッドシートからコピペする
^ → [ に置換(正規表現指定で先頭を[
$ → ], に置換(正規表現指定で行末を],
¥t, に置換(正規表現指定でタブを,

加工した結果をテストモジュールに貼り付ける
※場合によっては、特定の文字列を起こすスクリプトをElixirで作ってもいいですね(業務ではそうしてる)

diff_elixir:test/rectangle_test.exs
defmodule RectangleTest do
  use ExUnit.Case
  doctest Rectangle

  test "greets the world" do
    assert Rectangle.hello() == :world
  end

  test_params = [
-   [1, 1, 10, 10, 5, 5, 5, 5, true],
-   [1, 1, 10, 10, 15, 5, 5, 5, false]
+   [100, 100, 10, 10, 90, 90, 10, 10, true],
+   [100, 100, 10, 10, 90, 110, 10, 10, true],
+   [100, 100, 10, 10, 110, 90, 10, 10, true],
+   [100, 100, 10, 10, 110, 110, 10, 10, true],
+   [100, 100, 10, 10, 89, 89, 10, 10, false],
+   [100, 100, 10, 10, 89, 111, 10, 10, false],
+   [100, 100, 10, 10, 111, 89, 10, 10, false],
+   [100, 100, 10, 10, 111, 111, 10, 10, false],
+   [100, 100, 10, 10, 91, 91, 10, 10, true],
+   [100, 100, 10, 10, 91, 109, 10, 10, true],
+   [100, 100, 10, 10, 109, 91, 10, 10, true],
+   [100, 100, 10, 10, 109, 109, 10, 10, true],
+   [100, 100, 10, 10, 88, 88, 10, 10, false],
+   [100, 100, 10, 10, 88, 112, 10, 10, false],
+   [100, 100, 10, 10, 112, 88, 10, 10, false],
+   [100, 100, 10, 10, 112, 112, 10, 10, false],
+   [100, 100, 10, 10, 89, 90, 10, 10, false],
+   [100, 100, 10, 10, 90, 111, 10, 10, false],
+   [100, 100, 9, 10, 110, 90, 10, 10, false],
+   [100, 100, 10, 9, 110, 110, 10, 10, false],
+   [90, 90, 10, 10, 100, 100, 10, 10, true],
+   [90, 110, 10, 10, 100, 100, 10, 10, true],
+   [110, 90, 10, 10, 100, 100, 10, 10, true],
+   [110, 110, 10, 10, 100, 100, 10, 10, true],
+   [89, 89, 10, 10, 100, 100, 10, 10, false],
+   [89, 111, 10, 10, 100, 100, 10, 10, false],
+   [111, 89, 10, 10, 100, 100, 10, 10, false],
+   [111, 111, 10, 10, 100, 100, 10, 10, false],
+   [91, 91, 10, 10, 100, 100, 10, 10, true],
+   [91, 109, 10, 10, 100, 100, 10, 10, true],
+   [109, 91, 10, 10, 100, 100, 10, 10, true],
+   [109, 109, 10, 10, 100, 100, 10, 10, true],
+   [88, 88, 10, 10, 100, 100, 10, 10, false],
+   [88, 112, 10, 10, 100, 100, 10, 10, false],
+   [112, 88, 10, 10, 100, 100, 10, 10, false],
+   [112, 112, 10, 10, 100, 100, 10, 10, false],
+   [89, 90, 10, 10, 100, 100, 10, 10, false],
+   [90, 111, 10, 10, 100, 100, 10, 10, false],
+   [110, 90, 10, 10, 100, 100, 9, 10, false],
+   [110, 110, 10, 10, 100, 100, 10, 9, false]
  ]

  for test_param <- test_params do
    @test_param test_param
    test "collided #{inspect(@test_param)}" do
      [x1, y1, w1, h1, x2, y2, w2, h2, ret] = @test_param
      assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
    end
  end
end

実行結果

Randomized with seed 262009

Running tests...
Compiling 1 file (.ex)
...

  1) test collided [110, 90, 10, 10, 100, 100, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)



  2) test collided [100, 100, 10, 10, 110, 90, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

............

  3) test collided [100, 100, 10, 10, 90, 90, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

.

  4) test collided [100, 100, 10, 10, 110, 110, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

..

  5) test collided [90, 110, 10, 10, 100, 100, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

....

  6) test collided [110, 110, 10, 10, 100, 100, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

..

  7) test collided [90, 90, 10, 10, 100, 100, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

....

  8) test collided [100, 100, 10, 10, 90, 110, 10, 10, true] (RectangleTest)
     test/rectangle_test.exs:54
     Assertion with == failed
     code:  assert Rectangle.collided?(x1, y1, w1, h1, x2, y2, w2, h2) == ret
     left:  false
     right: true
     stacktrace:
       test/rectangle_test.exs:56: (test)

.......

Finished in 0.04 seconds (0.00s async, 0.04s sync)
2 doctests, 41 tests, 8 failures

Randomized with seed 233531

おお!失敗が8箇所
期待値と違うぞ
えっと… ぴったし重なった部分が判定が期待通りでない

プログラム修正

lib/rectangle.ex
defmodule Rectangle do
  @moduledoc """
  Documentation for `Rectangle`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Rectangle.hello()
      :world

  """
  def hello do
    :world
  end

  @doc """
  Collided detection.

  ## Examples

      iex> Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5)
      true

  """
  def collided?(x1, y1, w1, h1, x2, y2, w2, h2) do
-    x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2
+    x1 <= x2 + w2 && x1 + w1 >= x2 && y1 <= y2 + h2 && y1 + h1 >= y2
  end

end

実行結果

Running tests...
Compiling 1 file (.ex)
...........................................

Finished in 0.04 seconds (0.00s async, 0.04s sync)
2 doctests, 41 tests, 0 failures

Randomized with seed 251862

期待通り動くようになりました!

まとめ

・ChatGPTはすごいぞ!おおよその答えは合ってる(ホントもう少し)
・理解をしていないソースのコピペして終わらせる無責任なコーディングはやめましょう
 これはAI、他人のソース、ググった結果を鵜呑みにせずに検証しましょう
・テストは作りましょう、今回の実験の結果から、望んでいないケースもあります
・AIの技術はすばらしいと思います、でも利用側のリテラシーが必要である事を認識しました

21
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
3