GitHub Copilot Chatを試してみた

# 当記事の概要
先日、[GitHub Copilot Chat](https://github.blog/jp/2023-03-23-github-copilot-x-the-ai-powered-developer-experience/)のprivate betaが通ったので色々と試してみました。

# 前提環境
- macOS Ventura 13.3.1
- VSCode Insiders 1.79.0-insider
- (VSCode extension) GitHub Copilot Chat v0.1.2023053101

なお、2023年5月時点で、GitHub Copilot Chatを利用するには以下が必要となります。
- VSCodeのベータ版である [VSCode Insiders](https://code.visualstudio.com/insiders/)
- GitHub Copilot extensionのベータ版である [GitHub Copilot Nightly](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-nightly)
- GitHub Copilot Chatへのjoin


# JUnitテストケースの作成
Copilot Chatには、対象プログラムに対するテストコードを生成する機能があります。チャット欄に `/tests` と入力することで、表示中のプログラムのテストコードを作成してくれます。

以下の`Sample`クラスに対して、Copilot Chatでテストケースを作成してみます。

```java
package sample;

import java.util.Collections;
import java.util.List;

public class Sample {

    public int sum(int a, int b) {
        return a + b;
    }

    public int sub(int a, int b) {
        return a - b;
    }

    public List<String> sort(List<String> list) {
        Collections.sort(list);
        return list;
    }
}
```

`Sample`クラスを開いた状態で、Copilot Chatで `/tests` と入力し、出力されたテストコードが下記になります。

```java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SampleTest {

    @Test
    public void testSum() {
        Sample s = new Sample();
        assertEquals(5, s.sum(2, 3), "2 + 3 should equal 5");
        assertEquals(0, s.sum(0, 0), "0 + 0 should equal 0");
        assertEquals(-5, s.sum(-2, -3), "-2 + -3 should equal -5");
        assertEquals(10, s.sum(5, 5), "5 + 5 should equal 10");
    }

    @Test
    public void testSub() {
        Sample s = new Sample();
        assertEquals(-1, s.sub(2, 3), "2 - 3 should equal -1");
        assertEquals(0, s.sub(2, 2), "2 - 2 should equal 0");
        assertEquals(1, s.sub(3, 2), "3 - 2 should equal 1");
        assertEquals(-5, s.sub(-2, 3), "-2 - 3 should equal -5");
    }

    @Test
    public void testSort() {
        Sample s = new Sample();
        List<String> list = new ArrayList<>(Arrays.asList("c", "b", "a"));
        List<String> sortedList = s.sort(list);
        assertEquals(Arrays.asList("a", "b", "c"), sortedList, "List should be sorted in ascending order");
        List<String> list2 = new ArrayList<>(Arrays.asList("d", "c", "b", "a"));
        List<String> sortedList2 = s.sort(list2);
        assertEquals(Arrays.asList("a", "b", "c", "d"), sortedList2, "List should be sorted in ascending order");
    }
}
```

このテストコードは、`package` 指定が無いためにコンパイルエラーになりますが、そこだけ直してやることでコンパイルが通り、JUnitも正常に実行可能となりました。

2つの引数を足す`sum()`メソッド、引く`sub()`メソッドのテストコードは特に問題なく生成されています。

ソートする`sort()`メソッドも、ソートされることを確認するために c、b、a といったリストを準備し、`sort()`メソッドによりソートされることを確認しています。2つのリストを使用して確認していますが、1つめと2つめのテストデータであまり差異は無いため、2つ用意する意味はあまり無いように見えます。

今度は以下のコードを対象にテストコードを生成します。

```java
package sample;

public class Sample {

    public int calc(int a, int b) {
        if (a == 1) {
            return a;
        } else if (a == 2) {
            return a + 1;
        } else if (a < 10) {
            return a + 10;
        } else {
            return b;
        }
    }
}
```

`calc()`メソッドは、

- 引数aが1の場合は a を返す
- 引数aが2の場合は a + 1 を返す
- 引数aが10未満の場合は a + 10 を返す
- それ以外は引数bを返す

という内容です。

これに対して `/tests` で生成したテストコードが以下になります。

```java
package sample;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class SampleTest {

    @Test
    public void testCalcReturnsAWhenAEquals1() {
        Sample s = new Sample();
        int result = s.calc(1, 5);
        assertEquals(1, result, "calc should return a when a equals 1");
    }

    @Test
    public void testCalcReturnsAPlus1WhenAEquals2() {
        Sample s = new Sample();
        int result = s.calc(2, 5);
        assertEquals(3, result, "calc should return a + 1 when a equals 2");
    }

    @Test
    public void testCalcReturnsAPlus10WhenAIsLessThan10() {
        Sample s = new Sample();
        int result = s.calc(5, 5);
        assertEquals(15, result, "calc should return a + 10 when a is less than 10");
    }

    @Test
    public void testCalcReturnsBWhenAIsGreaterThan10() {
        Sample s = new Sample();
        int result = s.calc(15, 5);
        assertEquals(5, result, "calc should return b when a is greater than 10");
    }
}
```

コンパイルエラーはなく、テストは全て通ります。条件分岐で4種類のパターンがあり、それらは全て網羅されています。ただ、実際にテストケースを作る場合、`a < 10`という条件に対して、境界値である 9 と 10 で作るべきと思いますので、その点は満たせていないという結果でした。

また、`a = 5`のテストケース(`testCalcReturnsAPlus10WhenAIsLessThan10()`メソッド)では、a、bともに5となっていますが、`a = 5`のケースでは `a + 10` を返すことになっているため、aとbが同じ値では、`a + 10`を返しているのか、`b + 10`を返しているのかの判別がつきません。その意味でも、若干不十分なテストケースであると言えます。


# バグの検出
次に、プログラム中のバグを検出できるかを試してみます。対象とするサンプルコードは以下になります。

```java
package sample;

public class Sample {

    public int calc(int a, int b) {
        if (a == 1) {
            return a;
        } else if (a == 2) {
            return a + 1;
        } else if (a < 10) {
            return a + 10;
        } else if (a == 9) {
            return a + 9;
        } else {
            return b;
        }
    }
}
```

`calc()`メソッドでは、引数aの値によって返す値を変えていますが、`a = 9`の場合、`else if (a < 10)` が先に評価されて真になりreturnされるため、`else if (a == 9)` が真になるケースがありません。

このコードについて、Copilot Chatに「Sampleクラスに問題はありますか？」と尋ねた結果の回答が以下になります。

> コードを見る限り、問題はなさそうです。ただし、コードがどのように使用されるかによっては、問題があるかもしれません。例えば、引数aが1または2の場合には、特定の値を返すようになっています。これが意図した動作である場合は問題ありませんが、そうでない場合は問題があるかもしれません。

残念ながらバグの指摘には至りませんでした。

一方で、同じコードでGPT-4のChatGPTに聞いてみた結果が以下になります。こちらでは`a = 9`のケースの問題が指摘されており、GPT-4のためか精度の高い回答が得られています。

>はい、そのコードには論理的な問題が存在します。具体的には、if文の順序です。a < 10という条件がa == 9よりも先に評価されます。これは、aが9の時、最初のa < 10が真となり、a + 10が実行されます。つまり、aが9の場合でも、a + 9の部分は到達しません。


# CSVデータの作成
テストデータでCSVを作成するケースがあると思いますが、そういった場合にもCopilot Chatを活用することができます。

例えば、以下のような id, firstname, lastname, age の4つの項目からなるデータを100件生成させてみます。名前（firstname, lastname）は日本人の名前で、各行異なる値にします。

```
id,firstname,lastname,age
1,太郎,山田,29
2,吾郎,高橋,20
...
...
```

上記を生成するため、以下の入力文を与えてみます。

```
id, firstname, lastname, age という4つの項目を持つCSVデータを100件作成してください。
1行目はヘッダ行で、「id, firstname, lastname, age」をそのまま出力してください。
firstname, lastname は日本人の名前にしてください。
firstnameは全件異なる名前にしてください。
lastnameは全件異なる名字にしてください。
ageは0〜100の年齢です。
CSVデータを作成するプログラムではなく、CSVデータそのものを作成してください。
```

色々と試した結果、上記のようにある程度細かく指定する必要がありました。特に最後の「CSVデータを作成するプログラムではなく、CSVデータそのものを作成してください。」については、これを指定しないとデータではなくCSVを作成するプログラムを出力してしまうため付けています。

出力されたのは以下のCSVになります。

```
id,firstname,lastname,age
1,太郎,佐藤,23
2,花子,鈴木,45
3,健太,高橋,67
4,美和子,田中,12
5,大輔,渡辺,89
6,真理子,伊藤,34
7,雅子,山本,56
8,哲也,中村,78
9,恵子,小林,90
10,隆太郎,加藤,1
11,千春,吉田,23
12,和也,山田,45
13,美智子,佐々木,67
14,智之,山口,12
15,恭子,松本,89
16,健一,井上,34
17,美穂,木村,56
18,大介,林,78
19,真由美,斎藤,90
20,夏美,山崎,1
21,拓也,岡田,23
22,麻衣子,中島,45
23,健司,石川,67
24,美香,前田,12
25,智也,藤田,89
26,真紀,後藤,34
27,裕子,小川,56
28,浩二,菅原,78
29,由美子,平野,90
30,智美,岩崎,1
31,太一,宮崎,23
32,美咲,内田,45
33,健太郎,高山,67
34,真琴,小野,12
35,大樹,杉山,89
36,真由,吉川,34
37,美和,石田,56
38,大輝,原田,78
39,恵美子,小笠原,90
40,隆,荒木,1
41,千秋,村上,23
42,和美,西村,45
43,健一郎,福田,67
44,美沙子,太田,12
45,智彦,水野,89
46,真希,中川,34
47,裕美子,山内,56
48,浩太郎,市川,78
49,由美,柴田,90
50,智之介,石井,1
51,太郎,佐藤,23
52,花子,鈴木,45
53,健太,高橋,67
54,美和子,田中,12
55,大輔,渡辺,89
56,真理子,伊藤,34
57,雅子,山本,56
58,哲也,中村,78
59,恵子,小林,90
60,隆太郎,加藤,1
61,千春,吉田,23
62,和也,山田,45
63,美智子,佐々木,67
64,智之,山口,12
65,恭子,松本,89
66,健一,井上,34
67,美穂,木村,56
68,大介,林,78
69,真由美,斎藤,90
70,夏美,山崎,1
71,拓也,岡田,23
72,麻衣子,中島,45
73,健司,石川,67
74,美香,前田,12
75,智也,藤田,89
76,真紀,後藤,34
77,裕子,小川,56
78,浩二,菅原,78
79,由美子,平野,90
80,智美,岩崎,1
81,太一,宮崎,23
82,美咲,内田,45
83,健太郎,高山,67
84,真琴,小野,12
85,大樹,杉山,89
86,真由,吉川,34
87,美和,石田,56
88,大輝,原田,78
89,恵美子,小笠原,90
90,隆,荒木,1
91,千秋,村上,23
92,和美,西村,45
93,健一郎,福田,67
94,美沙子,太田,12
95,智彦,水野,89
96,真希,中川,34
97,裕美子,山内,56
98,浩太郎,市川,78
99,由美,柴田,90
100,智之介,石井,1
```

firstname, lastname は全件異なる値と指定したのですが、ちらほらと重複しているものも見られ、完璧には出力できていないようです。ただ、テストデータとして使うには十分な内容だと思います。


# 正規表現の作成
正規表現の作成に対応できるかを確認します。[正規表現の問題集1（基本編）](https://codezine.jp/article/detail/1573)に記載されている正規表現を作ってみます。

```
以下の条件に合致する文字列を検索する正規表現を作成してください。

文字列abを含んで、かつ、
文字列bcを含んで、かつ、
文字列cdを含む
```

これに対して、Copilot Chatは以下の出力を返しました。

```
以下の正規表現を使用することができます。

^(?=.*ab)(?=.*bc)(?=.*cd).*$

これは、肯定先読みを使用して、文字列の先頭から始まり、ab、bc、cdのすべてを含む文字列を検索します。
```

[正規表現の問題集1（基本編）](https://codezine.jp/article/detail/1573)に記載されている回答と同じ正規表現を出力してくれました。複雑な正規表現を作成したいときにも役立ちそうです。


# まとめ
GitHub Copilot Chatは、ChatGPTと同じく、チャット形式で自然文で与えた内容に沿ってコードやデータを出力したり、改善点を指摘してくれるという点で非常に有益なサービスであると思います。現状ではChatGPT（GPT-4）よりも出力の精度は高くないかもしれませんが、雛形の生成としては十分な機能を有していると思います。