🤖

TDDとGitHub Copilotで怠惰なプログラミングしちゃおうよ

2024/02/13に公開

こんにちは、最近はGitHub Copilotがいないとそわそわしてしまうようになってしまいました。
それに加えてTDDをしないと体が痒くなって来るようにもなってしまいました。
そわそわも体の痒みも止めるために合せ技で怠惰なプログラミングをしてやろう、という記事です。

TDDとGitHub Copilotの合せ技って?

ここでは詳細まで説明はしませんが、TDDは

  • レッド : 期待するテストを書き、テストが通らない状態
  • グリーン : とりあえず↑のテストが通る状態
  • リファクタリング : グリーンを保ちつつ、良いコードにしていく

のサイクルを回すことを指します。

そこで、GitHub Copilotを使いながらレッドの状態を作り、とりあえず通る実装を自分で行い、リファクタリングをGitHub Copilotにしてもらいます。

結果以下のようになります。

  • レッド : GitHub Copilot with 自分
  • グリーン : 自分 with GitHub Copilot
  • リファクタリング : GitHub Copilot

怠惰だなぁ、

前提

本記事の前提はRuby on Railsを使いつつ、テストはRspecで書きます。
また、以下のようなものを実装するとします。

  • Userというモデルがあり、複数のUserを束ねるOrganizationモデルがあるとする
  • Userの中で一番年齢が高いUserをOrganizationのオーナーとして返す関数を作成したい(年功序列w

User テーブル:

カラム名 データ型 説明
id integer ユーザーの一意のID
email string ユーザーのメールアドレス
age integer ユーザーの年齢
organization_id integer ユーザーが所属する組織のID
created_at datetime レコード作成日時
updated_at datetime レコード更新日時

Organization テーブル:

カラム名 データ型 説明
id integer 組織の一意のID
name string 組織の名前
created_at datetime レコード作成日時
updated_at datetime レコード更新日時
models/user.rb
class User < ApplicationRecord
  belongs_to :organization
end
models/organization.rb
class Organization < ApplicationRecord
  has_many :users
end

レッド1 : GitHub Copilot with 自分

まずは簡単なテストを書いていきます。
ここではすでにorganizationuserというFactoryが用意されてるとします

spec/models/organization_spec.rb
RSpec.describe Organization, type: :model do
  describe 'owner' do
    subject { organization.owner }

    let(:organization) { create(:organization) }
    let(:user) { create(:user, organization: organization, age: 25) }

    it 'ownerを返す' do
      is_expected.to eq user
    end
  end
end```

ここでも基本的にtabを多用します。
土台作りなのである程度は自分でも用意しないといい感じのサジェストはしてくれないですね。

グリーン1 : 自分 with GitHub Copilot

通るように実装します。一旦グリーンになればいいのでとりあえず動くようにusers.firstでも返します。

models/organization.rb
class Organization < ApplicationRecord
  has_many :users

  def owner
    users.first
  end
end

ここではリファクタリングをすることもないので、リファクタリングの工程は飛ばして再度レッドになるようにします。

レッド2 : GitHub Copilot with 自分

既存のuserunexpected_userにし、expected_userを追加します。
また、itの説明文にageが一番高いユーザーを返すと入れます。
tabを押す前がこちら

spec/models/organization_spec.rb
RSpec.describe Organization, type: :model do
  describe 'owner' do
    subject { organization.owner }

    let(:organization) { create(:organization) }
    let(:unexpected_user) { create(:user, organization: organization, age: 25) }
    let(:expected_user) { create(:user, organization: organization, age: 30) }

    it 'ageが一番高いユーザーを返す' do
      is_expected.to eq user
    end
  end
end

そしてis_expected.to eq userの行を消し、サジェストをそのまま入れると

spec/models/organization_spec.rb
RSpec.describe Organization, type: :model do
  describe 'owner' do
    subject { organization.owner }

    let(:organization) { create(:organization) }
    let(:unexpected_user) { create(:user, organization: organization, age: 25) }
    let(:expected_user) { create(:user, organization: organization, age: 30) }

    it 'ageが一番高いユーザーを返す' do
      is_expected.to eq expected_user
    end
  end
end

になります。無事is_expected.to eq expected_userをサジェストしてくれました。

グリーン2 : 自分 with GitHub Copilot

雑にageが一番高いユーザーを返すように実装します。
(今回はあえてリファクタリングができるようなcodeにしています)

models/organization.rb
class Organization < ApplicationRecord
  has_many :users

  def owner
    owner = nil
    users.each do |user|
      owner = user if owner.nil? || user.age > owner.age
    end
    owner
  end
end

さぁ、リファクタリングをしてもらいましょう。

リファクタリング : GitHub Copilot

owerの関数を選択してリファクタリングをお願いしましょう
指示
GitHub Copilotくんのリファクタリングしてくれた結果がこちら。
結果
いいですね〜怠惰ですね〜
order: :descなどを使うほうが早かったりもしそうなので、これもお願いしてみましょう
計算量

出来上がったcode

結果出来上がったcodeがこちらです。

models/organization.rb
class Organization < ApplicationRecord
  has_many :users

  def owner
    users.order(age: :desc).first
  end
end

感想

途中謎な実装(リファクタリングを挟みたかったため)なども自分でしましたが、結果いい感じのcodeに落ち着きました。本来のTDDではさらに異常系のテストなどを書いていき、もっとレッド=>グリーン=>リファクタリングのサイクルを回していくはずですが、今回は一旦ここまでにしておきます。

GitHub Copilotがないともうプログラミングができないほど怠惰な体になってしまいましたが、GitHub CopilotくんとTDDの相性は良さそうですね。
とはいえ、どんなcodeが望ましいかなどは実装者の頭にないとリファクタリングの工程で的確な指示はできないことも実感しました。

これからもGitHub Copilotくんと仲良くしていきます〜

SMARTCAMP Engineer Blog

Discussion