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

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

ChatGPTのサイトはこちら

https://chat.openai.com/chat

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

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

![ChatGPT (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/7edd2e61-30ab-2122-ece0-a853e1f41bae.png)

### せっかくなので、ちょっとだけ文章を入力して遊んでみました
![ChatGPT (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/82baf1c2-5d08-239c-e243-8266d15dc174.png)

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

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

まぁそうですよね

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

### こんな質問も
![Screenshot from 2022-12-06 21-59-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/5686ae7c-83c3-5797-ff8e-9f8d43f89884.png)
※月曜アキナゼミより

https://www.youtube.com/watch?v=L_y-KJq-ApM


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


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

![ほげ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/04a87e55-ec40-f4ba-2c1e-1dd2bd27ff6e.jpeg)
※検討使の選択とジャッパーンを戦いの舞台にするイメージ図

# つまりこの記事は
・ChatGPTでElixirのソース出力するとどうなるの？
・関数テストをしてみよう

の２つの視点で書きます

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

ChatGPTが出力した結果
![ChatGPT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/5661b4b4-0cb9-c882-57e8-78cda2eada79.png)

CharGPTの結果

```Elixir
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
```

```Elixir
Rectangle.collided?(1, 1, 10, 10, 5, 5, 5, 5)
```

# まずはiex起動して検証

文法的問題はなさそうで、動いているぽい（細かい検証は次で）
![Screenshot from 2022-12-06 20-39-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/88aab717-b785-5b49-8e33-dc7d9a64bc59.png)

# ではテストパターンを考えてみよう
ここではAとBの２つの正方形（又は長方形）の当たり判定を考えます

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

・2つの物体が重ならない状態
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/4d7569ad-e0bc-f366-21a4-0c96f765c661.png)

・２つの物体が重なる状態
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/483a9086-d029-0891-3e41-585bccdf9a56.png)

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

・境界線で重なっているパターン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/738104aa-2442-4648-a101-dc41e3976b2d.png)

・境界線付近で重ならないパターン
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/725983a4-7d46-da67-5bec-a5dc55dcca51.png)

### サイズの違いによるパターン
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/fffcde2e-ad01-d167-6af8-934e916848bb.png)

# テストプログラムを作ろう
### プロジェクトの作成

```sh
$ mix new rectangle
```
実行結果

```sh
* 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プロジェクトに移動

```sh
$ cd rectangle
```


### プロジェクトにテストの仕組みを組み込む
mix test.watchを使えるようにして、ファイル保存時に自動的にテストを行うようにします

参考

https://hexdocs.pm/mix_test_watch/readme.html

組み込んでみよう

mix.exsを編集

```diff_elixir: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の準備と実行

```sh
$ mix deps.get
```

実行結果

```sh
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の自動実行

```sh
$ 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が行われます

```diff_elixir: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に変化しました

### テストモジュールの作成

```diff_elixir
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と表示します

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

```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 "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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/70aa7388-83df-4ef7-cba8-a233d22fb09c.png)

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

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

```diff_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箇所
期待値と違うぞ
えっと…　ぴったし重なった部分が判定が期待通りでない

### プログラム修正

```diff_elixir: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の技術はすばらしいと思います、でも利用側のリテラシーが必要である事を認識しました