最近、Github Copilot の機能について調査をしておりまして、今回はテストコードの作成についてご紹介したいと思います。
テスト対象となるコードは以下になります。
内容はよくある計算処理をまとめたものになります。
ちなみに、こちらのコードはほぼ全て Github Copilot の補完機能で書きました。
class Calculation():
"""計算処理を行うクラス
"""
@classmethod
def calc_sum(cls, numList):
"""
引数のリストの合計値を求める
"""
# nummList が空の場合
if not numList:
return 0
# 引数が数字のみである事を確認
for num in numList:
if not isinstance(num, (int, float)):
return "引数には数値のみを入力してください"
# 合計値の演算結果を返す
return sum(numList)
@classmethod
def calc_average(cls, numList):
"""
引数のリストの平均値を求める
"""
# nummList が空の場合
if not numList:
return 0
# 引数が数字のみである事を確認
for num in numList:
if not isinstance(num, (int, float)):
return "引数には数値のみを入力してください"
# 平均値の演算結果を返す
return sum(numList) / len(numList)
@classmethod
def cosine_similarity(cls, x, y):
"""
コサイン類似度を求める
x: ベクトル
y: ベクトル
"""
# x, y が空の場合
if not x or not y:
return "ベクトルが空です"
# x, y に数値以外が含まれていないかを確認
for xi, yi in zip(x, y):
if not isinstance(xi, (int, float)) or not isinstance(yi, (int, float)):
return "ベクトルには数値のみを入力してください"
# x, y の次元数が異なる場合
if len(x) != len(y):
return "ベクトルの長さが等しくありません"
# x, y のノルムを求める
x_norm = sum([xi**2 for xi in x]) 0.5
y_norm = sum([yi2 for yi in y]) ** 0.5
# 内積を求める
dot_product = sum([xi yi for xi, yi in zip(x, y)])
# コサイン類似度の演算結果を返す
return dot_product / (x_norm y_norm)
@classmethod
def calc_vector(cls, x, y, operation):
"""
ベクトルの演算を行う
x: ベクトル
y: ベクトル
operation: 計算方法(和, 差, 内積)
"""
# x, y が空の場合
if not x or not y:
return "ベクトルが空です"
# x, y に数値以外が含まれていないかを確認
for xi, yi in zip(x, y):
if not isinstance(xi, (int, float)) or not isinstance(yi, (int, float)):
return "ベクトルには数値のみを入力してください"
# x, y の次元数が異なる場合
if len(x) != len(y):
return "ベクトルの長さが等しくありません"
# 計算方法が不正な場合
if operation not in ["和", "差", "内積"]:
return "計算方法が不正です"
# ベクトルの演算
if operation == "和":
return [xi + yi for xi, yi in zip(x, y)]
elif operation == "差":
return [xi - yi for xi, yi in zip(x, y)]
elif operation == "内積":
return sum([xi * yi for xi, yi in zip(x, y)])
ここから、テストコードを作成をしていきますが
今回は2パターンの作成方法をご紹介します。
①自動作成
以下の操作でテストコードを自動で作成してくれます。
Calculation() にカーソルを当てる -> 右クリック -> Copilot -> Generate Tests
作成されたコードは以下になります。
※import 等は割愛しています。
class TestCalculation(unittest.TestCase):
def test_calc_sum(self):
# Test case 1: Empty list
self.assertEqual(Calculation.calc_sum([]), 0)
# Test case 2: List with integers
self.assertEqual(Calculation.calc_sum([1, 2, 3, 4, 5]), 15)
# Test case 3: List with floats
self.assertEqual(Calculation.calc_sum([1.5, 2.5, 3.5, 4.5, 5.5]), 17.5)
# Test case 4: List with mixed integers and floats
self.assertEqual(Calculation.calc_sum([1, 2.5, 3, 4.5, 5]), 16)
# Test case 5: List with non-numeric values
self.assertEqual(Calculation.calc_sum([1, 2, '3', 4, 5]), "引数には数値のみを入力してください")
def test_calc_average(self):
# Test case 1: Empty list
self.assertEqual(Calculation.calc_average([]), 0)
# Test case 2: List with integers
self.assertEqual(Calculation.calc_average([1, 2, 3, 4, 5]), 3)
# Test case 3: List with floats
self.assertEqual(Calculation.calc_average([1.5, 2.5, 3.5, 4.5, 5.5]), 3.5)
# Test case 4: List with mixed integers and floats
self.assertEqual(Calculation.calc_average([1, 2.5, 3, 4.5, 5]), 3)
# Test case 5: List with non-numeric values
self.assertEqual(Calculation.calc_average([1, 2, '3', 4, 5]), "引数には数値のみを入力してください")
def test_cosine_similarity(self):
# Test case 1: Empty vectors
self.assertEqual(Calculation.cosine_similarity([], []), "ベクトルが空です")
# Test case 2: Vectors with integers
self.assertAlmostEqual(Calculation.cosine_similarity([1, 2, 3], [4, 5, 6]), 0.9746318461970762)
# Test case 3: Vectors with floats
self.assertAlmostEqual(Calculation.cosine_similarity([1.5, 2.5, 3.5], [4.5, 5.5, 6.5]), 0.9746318461970762)
# Test case 4: Vectors with mixed integers and floats
self.assertAlmostEqual(Calculation.cosine_similarity([1, 2.5, 3], [4.5, 5, 6]), 0.9746318461970762)
# Test case 5: Vectors with non-numeric values
self.assertEqual(Calculation.cosine_similarity([1, 2, '3'], [4, 5, 6]), "ベクトルには数値のみを入力してください")
# Test case 6: Vectors with different dimensions
self.assertEqual(Calculation.cosine_similarity([1, 2, 3], [4, 5]), "ベクトルの長さが等しくありません")
def test_calc_vector(self):
# Test case 1: Empty vectors
self.assertEqual(Calculation.calc_vector([], [], "和"), "ベクトルが空です")
# Test case 2: Vectors with integers (addition)
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5, 6], "和"), [5, 7, 9])
# Test case 3: Vectors with floats (subtraction)
self.assertEqual(Calculation.calc_vector([1.5, 2.5, 3.5], [4.5, 5.5, 6.5], "差"), [-3, -3, -3])
# Test case 4: Vectors with mixed integers and floats (dot product)
self.assertEqual(Calculation.calc_vector([1, 2.5, 3], [4.5, 5, 6], "内積"), 29.5)
# Test case 5: Vectors with non-numeric values
self.assertEqual(Calculation.calc_vector([1, 2, '3'], [4, 5, 6], "和"), "ベクトルには数値のみを入力してください")
# Test case 6: Vectors with different dimensions
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5], "和"), "ベクトルの長さが等しくありません")
# Test case 7: Invalid operation
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5, 6], "invalid"), "計算方法が不正です")
if __name__ == '__main__':
unittest.main()
ご覧の通り、各関数の各条件分岐まで網羅的に作成してくれています。
時間も数秒程しかかからず作成してくれましたので、非常に便利ですね^^
しかし、以下2点の問題があります。
・失敗ケースが存在する場合がある
Github Copilot が全て作成しているにも関わらず、想定結果と実際の結果に相違があるケースがいくつかありました。
・仕様を考慮できていない場合がある
テストケースの想定結果でそもそも関数の仕様(返り値の型など)を考慮できていないケースも同様にいくつかありました。
②コメントベースで設計
コメントでテストケースを設計し、その内容に応じたテストケースを作成する事も可能です。
まずは、作成された以下のコードをご覧下さい。
class CalculationPositiveTests(unittest.TestCase):
# Calculation の正常系テスト
"""
テストケース
calc_sum
- numList が数字のみ
calc_average
- numList が数字のみ
cosine_similarity
- x, y のベクトルの長さが等しい
calc_vector
- x, y のベクトルの長さが等しい
- operation: 和
- operation: 差
- operation: 内積
"""
def test_calc_sum(self):
# Test case 1: List with integers
self.assertEqual(Calculation.calc_sum([1, 2, 3, 4, 5]), 15)
# Test case 2: List with floats
self.assertEqual(Calculation.calc_sum([1.5, 2.5, 3.5, 4.5, 5.5]), 17.5)
# Test case 3: List with mixed integers and floats
self.assertEqual(Calculation.calc_sum([1, 2.5, 3, 4.5, 5]), 16)
def test_calc_average(self):
# Test case 1: List with integers
self.assertEqual(Calculation.calc_average([1, 2, 3, 4, 5]), 3)
# Test case 2: List with floats
self.assertEqual(Calculation.calc_average([1.5, 2.5, 3.5, 4.5, 5.5]), 3.5)
# Test case 3: List with mixed integers and floats
self.assertEqual(Calculation.calc_average([1, 2.5, 3, 4.5, 5]), 3)
def test_cosine_similarity(self):
# Test case 1: Vectors with integers
self.assertAlmostEqual(Calculation.cosine_similarity([1, 2, 3], [4, 5, 6]), 0.9746318461970762)
# Test case 2: Vectors with floats
self.assertAlmostEqual(Calculation.cosine_similarity([1.5, 2.5, 3.5], [4.5, 5.5, 6.5]), 0.9746318461970762)
# Test case 3: Vectors with mixed integers and floats
self.assertAlmostEqual(Calculation.cosine_similarity([1, 2.5, 3], [4.5, 5, 6]), 0.9746318461970762)
def test_calc_vector(self):
# Test case 1: Vectors with equal dimensions
# Test case 1-1: operation: 和
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5, 6], "和"), [5, 7, 9])
# Test case 1-2: operation: 差
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5, 6], "差"), [-3, -3, -3])
# Test case 1-3: operation: 内積
self.assertEqual(Calculation.calc_vector([1, 2, 3], [4, 5, 6], "内積"), 32)
class CalculationNegativeTests(unittest.TestCase):
# Calculation の異常系テスト
"""
テストケース
calc_sum
- numList が空
- numList に数値以外が含まれる
calc_average
- numList が空
- numList に数値以外が含まれる
cosine_similarity
- x, y のベクトルの長さが異なる
- x, y の何れかが空
- x, y に数値以外が含まれる
calc_vector
- x, y のベクトルの長さが異なる
- x, y の何れかが空
- x, y に数値以外が含まれる
- operation が不正
"""
def test_calc_sum(self):
# Test case 1: Empty list
with self.assertRaises(ValueError):
Calculation.calc_sum([])
# Test case 2: List with non-numeric elements
with self.assertRaises(ValueError):
Calculation.calc_sum([1, 2, 3, 4, "a"])
def test_calc_average(self):
# Test case 1: Empty list
with self.assertRaises(ValueError):
Calculation.calc_average([])
# Test case 2: List with non-numeric elements
with self.assertRaises(ValueError):
Calculation.calc_average([1, 2, 3, 4, "a"])
def test_cosine_similarity(self):
# Test case 1: Vectors with different dimensions
with self.assertRaises(ValueError):
Calculation.cosine_similarity([1, 2, 3], [4, 5])
# Test case 2: Empty vector
with self.assertRaises(ValueError):
Calculation.cosine_similarity([], [4, 5, 6])
# Test case 3: Vector with non-numeric elements
with self.assertRaises(ValueError):
Calculation.cosine_similarity([1, 2, "a"], [4, 5, 6])
def test_calc_vector(self):
# Test case 1: Vectors with different dimensions
with self.assertRaises(ValueError):
Calculation.calc_vector([1, 2, 3], [4, 5], "和")
# Test case 2: Empty vector
with self.assertRaises(ValueError):
Calculation.calc_vector([], [4, 5, 6], "和")
# Test case 3: Vector with non-numeric elements
with self.assertRaises(ValueError):
Calculation.calc_vector([1, 2, "a"], [4, 5, 6], "和")
# Test case 4: Invalid operation
with self.assertRaises(ValueError):
Calculation.calc_vector([1, 2, 3], [4, 5, 6], "invalid")
例えば、CalculationNegativeTests のテストを作成する際は、以下をプログラマーが書くだけで、あとは Github Copilot が作成してくれる、というものです。
class CalculationPositiveTests(unittest.TestCase):
# Calculation の正常系テスト
"""
テストケース
calc_sum
- numList が数字のみ
calc_average
- numList が数字のみ
cosine_similarity
- x, y のベクトルの長さが等しい
calc_vector
- x, y のベクトルの長さが等しい
- operation: 和
- operation: 差
- operation: 内積
"""
このように簡単にテストケースを設計するだけで、作成してくれるのはやはり便利ですね^^
しかし、こちらもやはり「①自動作成」と同様の問題が確認できました。
従って、どちらのケースも完全に Github Copilot に書かせるのは難しそうです。。。
以上、Github Copilot にテストコードを作成させる方法についてご紹介致しました。
細かく設計する必要があるのであれば、「②コメントベースで設計」を利用しても良いですし、とりあえず最低限網羅したケースがあれば良いのであれば「①自動作成」を使うやり方も有効かも知れません。
もしくは両方を組み合わせて使うのも良いかも知れません。
ぜひ、ご参考に頂ければ幸いです^^