8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SalesforceAdvent Calendar 2022

Day 8

ChatGPTにApexテストを書かせてみたら使えそうだった

Last updated at Posted at 2022-12-08

昨日書いたChatGPTにApexトリガーを書かせた記事の続きです。

しかし、改めて見てみると、staticメソッドをインスタンスメソッドとして呼んでいたり間違っているところがありますね。

今回は前回ChatGPTに書いてもらったApexトリガーのテストをChatGPTに書いてもらおうと思います。

おまけ:最初の指示に「考えられるテストケースを全部網羅してください。」と加えてみる」が一番すごいので時間のない方はいきなりそちらをお読みください。

Apexトリガーのテストを書いてもらう

Q1. 取引先の請求先住所が変更されたときにGoogle Geocoding APIを実行し緯度経度を取得し保存するApexトリガーを以下のように実装しました。これらのコードのテストコードを実装してください。

この後ろに生成してもらった全ソースコードを貼りました。

A1. ChatGPTの回答

※前回は証拠としてスクショも貼っていましたが、今回は貼りません。

テストコードを実装するには、まず、データを準備する必要があります。次に、このコードが正しく実行され、取引先の請求先住所が更新されたときに、取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されるかどうかを確認する必要があります。

以下は、取引先のテストデータを作成し、トリガーが正しく実行され、取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されるかどうかを確認するテストコードの例です。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  @isTest
  static void testGeocodingTrigger() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);
    createTestAccount('Test Account 2', 'Test Street 2', 'Test City 2', 'Test State 2', 'Test Postal Code 2', 'Test Country 2', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name IN ('Test Account 1', 'Test Account 2')];
    Map<Id, Account> oldAccountMap = new Map<Id, Account>();
    for (Account account : accounts) {
      oldAccountMap.put(account.Id, account.clone());
    }

    // トリガーをテストする
    Test.startTest();
    Geocoding.updateGeocoding(accounts);
    Test.stopTest();

    // 取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されることを確認する
    for (Account account : accounts) {
      Account oldAccount = oldAccountMap.get(account.Id);
      if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry) {
        System.assertNotEquals(null, account.BillingLatitude, 'BillingLatitude should not be null');
        System.assertNotEquals(null, account.BillingLongitude, 'BillingLongitude should not be null');
      }
    }
  }
}

上記のテストコードは、取引先のテストデータを作成し、トリガーが正しく実行され、取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されるかどうかを確認するものです。

A1.の感想

Geocoding APIを直接実行してしまっているので、そこはスタブにして欲しいですね。
他にも気になるところはありますが、とりあえずそこを直してもらいましょう。

Q2. Geocoding APIの呼び出しはスタブで行って欲しいです。

A2. ChatGPTの回答

可能です。上記のテストコードに、Geocoding APIの呼び出しをスタブ化する処理を追加することで、Geocoding APIの呼び出しをスタブ化することができます。

例えば、次のようにして、Geocoding APIの呼び出しをスタブ化することができます。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  @isTest
  static void testGeocodingTrigger() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);
    createTestAccount('Test Account 2', 'Test Street 2', 'Test City 2', 'Test State 2', 'Test Postal Code 2', 'Test Country 2', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name IN ('Test Account 1', 'Test Account 2')];
    Map<Id, Account> oldAccountMap = new Map<Id, Account>();
    for (Account account : accounts) {
      oldAccountMap.put(account.Id, account.clone());
    }

    // Geocoding APIの呼び出しをスタブ化する
    GoogleGeocodingAPI.GEOCODING_API_KEY = 'Test API Key';
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'Test API Endpoint';
    GoogleGeocodingAPI.GeocodingResponse geocodingResponse = new GoogleGeocodingAPI.GeocodingResponse();
    geocodingResponse.status = 'OK';
    geocodingResponse.results = new List<GoogleGeocodingAPI.Result>();
    GoogleGeocodingAPI.Result result = new GoogleGeocodingAPI.Result();
    result.geometry = new GoogleGeocodingAPI.Geometry();
    result.geometry.location = new GoogleGeocodingAPI.Location();
    result.geometry.location.lat = 35.6586;
    result.geometry.location.lng = 139.7454;
    geocodingResponse.results.add(result);
    Test.setMock(HttpCalloutMock.class, new GoogleGeocodingAPIMock(geocodingResponse));

    // トリガーをテストする
    Test.startTest();
    Geocoding.updateGeocoding(accounts);
    Test.stopTest();

    // 取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されることを確認する
    for (Account account : accounts) {
      Account oldAccount = oldAccountMap.get(account.Id);
      if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry) {
        System.assertEquals(35.6586, account.BillingLatitude, 'BillingLatitude should be 35.6586');
        System.assertEquals(139.7454, account.BillingLongitude, 'BillingLongitude should be 139.7454');
      }
    }
  }

  // Geocoding APIの呼び出しをスタブ化するためのHttpCalloutMockクラス
  private class GoogleGeocodingAPIMock implements HttpCalloutMock {
    GoogleGeocodingAPI.GeocodingResponse response;

    public GoogleGeocodingAPIMock(GoogleGeocodingAPI.GeocodingResponse response) {
      this.response = response;
    }

    public HTTPResponse respond(HTTPRequest request) {
      HttpResponse res = new HttpResponse();
      res.setHeader('Content-Type', 'application/json');
      res.setBody(JSON.serialize(response));
      res.setStatusCode(200);
      return res;
    }
  }
}

A2.の感想

ちゃんとHttpCalloutMockが使えてる。賢い。

次はupdateGeocodingのテストにしかなっていないことを指摘しましょう。

Q3. しかし、これだとupdateGeocodingのテストしか書けていないのでトリガー自体のテストを書いて欲しいです。

A3. ChatGPTの回答

可能です。上記のテストコードに、トリガーをテストする処理を追加することで、トリガー自体もテストすることができます。

例えば、次のようにして、トリガーをテストすることができます。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  @isTest
  static void testGeocodingTrigger() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);
    createTestAccount('Test Account 2', 'Test Street 2', 'Test City 2', 'Test State 2', 'Test Postal Code 2', 'Test Country 2', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name IN ('Test Account 1', 'Test Account 2')];
    Map<Id, Account> oldAccountMap = new Map<Id, Account>();
    for (Account account : accounts) {
      oldAccountMap.put(account.Id, account.clone());
    }

    // Geocoding APIの呼び出しをスタブ化する
    GoogleGeocodingAPI.GEOCODING_API_KEY = 'Test API Key';
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'Test API Endpoint';
    GoogleGeocodingAPI.GeocodingResponse geocodingResponse = new GoogleGeocodingAPI.GeocodingResponse();
    geocodingResponse.status = 'OK';
    geocodingResponse.results = new List<GoogleGeocodingAPI.Result>();
    GoogleGeocodingAPI.Result result = new GoogleGeocodingAPI.Result();
    result.geometry = new GoogleGeocodingAPI.Geometry();
    result.geometry.location = new GoogleGeocodingAPI.Location();
    result.geometry.location.lat = 35.6586;
    result.geometry.location.lng = 139.7454;
    geocodingResponse.results.add(result);
    Test.setMock(HttpCalloutMock.class, new GoogleGeocodingAPIMock(geocodingResponse));

    // トリガーをテストする
    Test.startTest();
    GeocodingTrigger.GeocodingTrigger trigger = new GeocodingTrigger.GeocodingTrigger();
    trigger.afterUpdate(accounts, oldAccountMap);
    Test.stopTest();

    // 取引先の請求先住所から正しく緯度経度が取得され、取引先のレコードに保存されることを確認する
    for (Account account : accounts) {
      Account oldAccount = oldAccountMap.get(account.Id);
      if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry
      ) {
        System.assertEquals(35.6586, account.BillingLatitude, 'BillingLatitude should be 35.6586');
        System.assertEquals(139.7454, account.BillingLongitude, 'BillingLongitude should be 139.7454');
      }
    }
  }

  // Geocoding APIの呼び出しをスタブ化するためのHttpCalloutMockクラス
  private class GoogleGeocodingAPIMock implements HttpCalloutMock {
    GoogleGeocodingAPI.GeocodingResponse response;
    public GoogleGeocodingAPIMock(GoogleGeocodingAPI.GeocodingResponse response) {
      this.response = response;
    }

    public HTTPResponse respond(HTTPRequest request) {
      HttpResponse res = new HttpResponse();
      res.setHeader('Content-Type', 'application/json');
      res.setBody(JSON.serialize(response));
      res.setStatusCode(200);
      return res;
    }
  }
}

上記の例では、GeocodingTriggerTestクラスに、testGeocodingTriggerメソッドを定義しました。このメソッドでは、GeocodingTriggerトリガーをテストするための準備を行い、GeocodingTriggerトリガーを実行し、期待される結果が得られることを確認する処理を記述します。また、GeocodingTriggerトリガーが呼び出すGeocoding.updateGeocodingメソッドも同時にテストすることができます。

上記の例では、Test.startTestとTest.stopTestメソッドを使って、トリガーを実行する処理をテストメソッド内で実行しています。Test.startTestメソッドでは、トリガーを実行する処理を開始し、Test.stopTestメソッドでは、トリガーを実行する処理を終了します。これらのメソッドを使用することで、トリガーを実際に実行する際と同様の状態でトリガーをテストすることができます。

これらの例を参考に、以下のコードのテストコードを実装してみてください。

なぜか元のGoogleGeocodingAPIのコードが貼られる

A3.の感想

惜しい。住所が変更されたときに動くというロジックがテストできていない。

そしてなぜ俺にテストコードを実装させようとしてきた?一方的に指示されるのが嫌になってきたのか?

Q4. 請求先住所が変更されたときに緯度経度が保存されるというテストを書いて欲しいのです。このテストでは住所が変更されたときに処理が実行されるかがテストできていません。

A4. ChatGPTの回答

可能です。上記のテストコードに、取引先の請求先住所が変更されたときに緯度経度が保存されることをテストする処理を追加することで、取引先の請求先住所が変更されたときに緯度経度が保存されることを確認することができます。

例えば、次のようにして、取引先の請求先住所が変更されたときに緯度経度が保存されることをテストすることができます。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  @isTest
  static void testGeocodingTrigger() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);
    createTestAccount('Test Account 2', 'Test Street 2', 'Test City 2', 'Test State 2', 'Test Postal Code 2', 'Test Country 2', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name IN ('Test Account 1', 'Test Account 2')];
    Map<Id, Account> oldAccountMap = new Map<Id, Account>();
    for (Account account : accounts) {
      oldAccountMap.put(account.Id, account.clone());
    }

    // Geocoding APIの呼び出しをスタブ化する
    GoogleGeocodingAPI.GEOCODING_API_KEY = 'Test API Key';
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'Test API Endpoint';
    GoogleGeocodingAPI.GeocodingResponse response = new GoogleGeocodingAPI.GeocodingResponse();
    response.status = 'OK';
    response.results = new List<GoogleGeocodingAPI.GeocodingResponse.Result>();
    response.results.add(new GoogleGeocodingAPI.GeocodingResponse.Result());
    response.results[0].geometry = new GoogleGeocodingAPI.GeocodingResponse.Geometry();
    response.results[0].geometry.location = new GoogleGeocodingAPI.GeocodingResponse.Location();
    response.results[0].geometry.location.lat = 35.6586;
    response.results[0].geometry.location.lng = 139.7454;
    Test.setMock(HttpCalloutMock.class, new GoogleGeocodingAPIMock(response));

    // トリガーが実行されることを検証する
    accounts[0].BillingStreet = 'Test Street 1 Updated';
    update accounts[0];

    accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name = 'Test Account 1'];
    System.assertEquals(35.6586, accounts[0].BillingLatitude, 'BillingLatitude should be 35.6586');
    System.assertEquals(139.7454, accounts[0].BillingLongitude, 'BillingLongitude should be 139.7454');
  }

  // Geocoding APIの呼び出しをスタブ化するためのHttpCalloutMockクラス
  private class GoogleGeocodingAPIMock implements HttpCalloutMock {
    GoogleGeocodingAPI.GeocodingResponse response;

    public GoogleGeocodingAPIMock(GoogleGeocodingAPI.GeocodingResponse response) {
      this.response = response;
    }

    public HTTPResponse respond(HTTPRequest request) {
      HttpResponse res = new HttpResponse();
      res.setHeader('Content-Type', 'application/json');
      res.setBody(JSON.serialize(response));
      res.setStatusCode(200);
      return res;
    }
  }
}

上記のテストコードでは、最初に2つの取引先を作成し、その後、1つの取引先の請求先住所を更新します。更新後に、更新された取引先の緯度経度が正しく保存されているかを検証しています。

また、Geocoding APIの呼び出しをスタブ化することで、実際にAPIを呼び出すことなく、トリガーが正しく動作するかをテストすることができます。

A4.の感想

ああ、そうね。いいんじゃないかい。

次は住所を変更しないときは処理が呼ばれないことをテストしてもらおう。

Q5. レコードが更新されても住所が変更されない場合は処理が実行されないことを確認するテストを実装してください。追加するメソッドの部分だけ返答してください。

A5. ChatGPTの回答

以下のようなメソッドを追加することで、取引先の請求先住所が変更されない場合に処理が実行されないことをテストすることができます。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  // 取引先の請求先住所が変更されない場合に処理が実行されないことを確認するメソッド
  @isTest
  static void testGeocodingTriggerWithoutAddressChange() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name = 'Test Account 1'];
    Map<Id, Account> oldAccountMap = new Map<Id, Account>();
    for (Account account : accounts) {
      oldAccountMap.put(account.Id, account.clone());
    }

    // Geocoding APIの呼び出しをスタブ化する
    GoogleGeocodingAPI.GEOCODING_API_KEY = 'Test API Key';
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'Test API Endpoint';
    GoogleGeocodingAPI.GeocodingResponse response = new GoogleGeocodingAPI.GeocodingResponse();
    response.status = 'OK';
    response.results = new List<GoogleGeocodingAPI.GeocodingResponse.Result>();
    response.results.add(new GoogleGeocodingAPI.GeocodingResponse.Result());
    response.results[0].geometry = new GoogleGeocodingAPI.GeocodingResponse.Geometry();
    response.results[0].geometry.location = new GoogleGeocodingAPI.GeocodingResponse.Location();
    response.results[0].geometry.location.lat = 35.6586;
    response.results[0].geometry.location.lng = 139.7454;
    Test.setMock(HttpCalloutMock.class, new GoogleGeocodingAPIMock(response));

    // トリガーが実行されないことを検証する
    update accounts[0];

    accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name = 'Test Account 1'];
    System.assertEquals(null, accounts[0].BillingLatitude, 'BillingLatitude should be null');
    System.assertEquals(null, accounts[0].BillingLongitude, 'BillingLongitude should be null');
  }

  // 略
}

上記のテストコードでは、最初に1つの取引先を作成し、その後、取引先の住所を更新しません。更新後に、取引先の緯度経度が変更されていないかを検証しています。

これで、取引先の請求先住所が変更されない場合に処理が実行されないことを確認することができます。

A5.の感想

うん。悪くないね。

追加メソッドだけ書いてという指示は微妙な反映度ではある。

しかし、oldAccountMapって使ってなくね。

Q6. oldAccountMapは使っていないと思いますので、該当箇所を削除してください。createTestAccountとGoogleGeocodingAPIMockは変更ないと思うので省略して返答してください。

A6. ChatGPTの回答

次のように修正することで、oldAccountMapを使わないようになります。

@isTest
private class GeocodingTriggerTest {
  // テストデータを作成するためのヘルパーメソッド
  static void createTestAccount(String name, String billingStreet, String billingCity, String billingState, String billingPostalCode, String billingCountry, Double billingLatitude, Double billingLongitude) {
    Account account = new Account(Name = name, BillingStreet = billingStreet, BillingCity = billingCity, BillingState = billingState, BillingPostalCode = billingPostalCode, BillingCountry = billingCountry, BillingLatitude = billingLatitude, BillingLongitude = billingLongitude);
    insert account;
  }

  // 取引先の請求先住所が変更されない場合に処理が実行されないことを確認するメソッド
  @isTest
  static void testGeocodingTriggerWithoutAddressChange() {
    // テストデータを作成する
    createTestAccount('Test Account 1', 'Test Street 1', 'Test City 1', 'Test State 1', 'Test Postal Code 1', 'Test Country 1', null, null);

    // トリガーをテストするためのデータを準備する
    List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude FROM Account WHERE Name = 'Test Account 1'];

    // Geocoding APIの呼び出しをスタブ化する
    GoogleGeocodingAPI.GEOCODING_API_KEY = 'Test API Key';
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'Test API Endpoint';
    GoogleGeocodingAPI.GeocodingResponse response = new GoogleGeocodingAPI.GeocodingResponse();
    response.status = 'OK';
    response.results = new List<GoogleGeocodingAPI.GeocodingResponse.Result>();
    response.results.add(new GoogleGeocodingAPI.GeocodingResponse.Result());
    response.results[0].geometry = new GoogleGeocodingAPI.GeocodingResponse.Geometry();
    response.results[0].geometry.location = new GoogleGeocodingAPI.GeocodingResponse.Location();
    response.results[0].geometry.location.lat = 35.6586;
    response.results[0].geometry.location.lng = 139.7454;

    Test.setMock(HttpCalloutMock.class, new GoogleGeocodingAPIMock(response));

    // トリガーが実行されないことを検証する
    update accounts[0];

    accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude,
    BillingLongitude FROM Account WHERE Name = 'Test Account 1'];
    System.assertEquals(null, accounts[0].BillingLatitude, 'BillingLatitude should be null');
    System.assertEquals(null, accounts[0].BillingLongitude, 'BillingLongitude should be null');
  }

  // 略
}

oldAccountMapを使わないように修正したので、取引先の請求先住所が変更されない場合に処理が実行されないことを確認することができるようになりました。

A6.の感想

createTestAccountは省略していいと言ったのに省略してくれないな。

testGeocodingTriggerも修正して欲しかったが、あの指示では伝わらなかったか。

もう面倒になってきたので、こんなところにしておこう。

おまけ:最初の指示に「考えられるテストケースを全部網羅してください。」と加えてみる

テストコードの生成はけっこう使えるかもしれませんね。
テストコード作成って割りと作業だったりするので、ある程度書いてくれるだけでもありがたい。
テストケースの指示はしないといけなそうだが、

と「終わりに」の文章を書いていて、思った。「もしかして、テストケースを全網羅しろ」って指示したらいいのでは?

最後にやってみよう。

Q1. 最初の指示+ 「考えられるテストケースを全部網羅してください。」

A1. ChatGPTの回答

以下がテストコードの例です。

// GeocodingTriggerTest.cls

@isTest
private class GeocodingTriggerTest {
  @isTest
  static void testGeocodingTrigger() {
    // 新しいアカウントを作成
    Account account = new Account(
      Name = 'Test Account',
      BillingStreet = '123 Main St.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10001',
      BillingCountry = 'United States'
    );
    insert account;

    // アカウントの請求先住所を変更
    account.BillingStreet = '456 Park Ave.';
    account.BillingCity = 'New York';
    account.BillingState = 'NY';
    account.BillingPostalCode = '10022';
    account.BillingCountry = 'United States';
    update account;

    // アカウントの請求先住所が変更されたかどうかを検証
    Account updatedAccount = [SELECT BillingLatitude, BillingLongitude FROM Account WHERE Id = :account.Id];
    System.assertNotEquals(null, updatedAccount.BillingLatitude);
    System.assertNotEquals(null, updatedAccount.BillingLongitude);
  }
}

考えられるテストケースとしては、以下が挙げられます。

  • アカウントの請求先住所が変更された場合に、緯度経度が正しく更新されるかどうか
  • アカウントの請求先住所が変更されなかった場合に、緯度経度が更新されないかどうか
  • Google Geocoding APIからエラーが返された場合に、緯度経度が更新されないかどうか
  • Google Geocoding APIから空のレスポンスが返された場合に、緯度経度が更新されないかどうか
  • 複数のアカウントが同時に更新された場合に、各アカウントの緯度経度が正しく更新されるかどうか
  • 削除済みのアカウントが更新された場合に、トリガーが正しく動作するかどうか

A1.の感想

テストケースを箇条書きで書いてきたか。

実装してもらおう。

Q2. すべてのテストケースを実装してください。

A2. ChatGPTの回答

以下が、すべてのテストケースを実装したテストコードの例です。

// GeocodingTriggerTest.cls

@isTest
private class GeocodingTriggerTest {
  @isTest
  static void testGeocodingTrigger() {
    // 新しいアカウントを作成
    Account account = new Account(
      Name = 'Test Account',
      BillingStreet = '123 Main St.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10001',
      BillingCountry = 'United States'
    );
    insert account;

    // アカウントの請求先住所を変更
    account.BillingStreet = '456 Park Ave.';
    account.BillingCity = 'New York';
    account.BillingState = 'NY';
    account.BillingPostalCode = '10022';
    account.BillingCountry = 'United States';
    update account;

    // アカウントの請求先住所が変更されたかどうかを検証
    Account updatedAccount = [SELECT BillingLatitude, BillingLongitude FROM Account WHERE Id = :account.Id];
    System.assertNotEquals(null, updatedAccount.BillingLatitude);
    System.assertNotEquals(null, updatedAccount.BillingLongitude);

    // 同じアカウントを更新して、住所が変更されなかったことを検証
    update account;
    updatedAccount = [SELECT BillingLatitude, BillingLongitude FROM Account WHERE Id = :account.Id];
    System.assertEquals(account.BillingLatitude, updatedAccount.BillingLatitude);
    System.assertEquals(account.BillingLongitude, updatedAccount.BillingLongitude);

    // アカウントを削除
    delete account;

    // 削除済みのアカウントを更新して、トリガーが正しく動作するかを検証
    update account;
  }

  @isTest
  static void testGetAccountsForGeocoding() {
    // 新しいアカウントを作成
    Account account1 = new Account(
      Name = 'Test Account 1',
      BillingStreet = '123 Main St.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10001',
      BillingCountry = 'United States'
    );
    Account account2 = new Account(
      Name = 'Test Account 2',
      BillingStreet = '456 Park Ave.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10022',
      BillingCountry = 'United States'
    );
    insert account

    // 2つのアカウントを更新
    account1.BillingStreet = '789 Broadway';
    account2.BillingStreet = '123 Main St.';
    update account1, account2;

    // 住所が変更されたアカウントのみを取得することを検証
    Map<Id, Account> oldAccountMap = new Map<Id, Account>{
      account1.Id => account1,
      account2.Id => account2
    };
    List<Account> newAccounts = [SELECT Id, BillingStreet FROM Account WHERE Id IN :oldAccountMap.keySet()];
    List<Account> accounts = Geocoding.getAccountsForGeocoding(newAccounts, oldAccountMap);
    System.assertEquals(1, accounts.size());
    System.assertEquals(account1.Id, accounts[0].Id);

    // 全てのアカウントを削除
    delete account1, account2;
  }

  @isTest
  static void testUpdateGeocoding() {
    // 新しいアカウントを作成
    Account account1 = new Account(
      Name = 'Test Account 1',
      BillingStreet = '123 Main St.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10001',
      BillingCountry = 'United States'
    );
    Account account2 = new Account(
      Name = 'Test Account 2',
      BillingStreet = '456 Park Ave.',
      BillingCity = 'New York',
      BillingState = 'NY',
      BillingPostalCode = '10022',
      BillingCountry = 'United States'
    );
    insert account1, account2;

    // 2つのアカウントを更新
    account1.BillingStreet = '789 Broadway';
    account2.BillingStreet = '123 Main St.';
    update account1, account2;

    // 住所が変更されたアカウントのみを取得し、緯度経度が更新されることを検証
    List<Account> accounts = [SELECT Id, BillingStreet, BillingLatitude, BillingLongitude FROM Account WHERE Id IN :account1.Id, account2.Id];
    Geocoding.updateGeocoding(accounts);
    for (Account account : accounts) {
      if (account.BillingStreet == '789 Broadway') {
        System.assertNotEquals(null, account.BillingLatitude);
        System.assertNotEquals(null, account.BillingLongitude);
      } else {
        System.assertEquals(null, account.BillingLatitude);
        System.assertEquals(null, account.BillingLongitude);
      }
    }

    // 全てのアカウントを削除
    delete account1, account2;
  }

  @isTest
  static void testGoogleGeocodingAPI() {
    // テスト用のモッククラスを定義
    class MockHttp implements Http {
      public HttpResponse send(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        if (req.getEndpoint().contains('error')) {
          // レスポンスにエラーを設定
          res.setBody('{"status": "ERROR", "error_message": "Test error message"}');
        } else if (req.getEndpoint().contains('empty')) {
          // レスポンスに空のレスポンスを設定
          res.setBody('{"status": "OK", "results": []}');
        } else {
          // レスポンスに正常なレスポンスを設定
          res.setBody('{"status": "OK", "results": [{"geometry": {"location": {"lat": 40.730610, "lng": -73.935242}}}]}');
        }
        return res;
      }
    }

    // モッククラスを設定
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = 'https://maps.googleapis.com/maps/api/geocode/json?address=test&key=test';
    GoogleGeocodingAPI.Http http = new MockHttp();
    GoogleGeocodingAPI.setMock(http);

    // 正常なレスポンスを取得することを検証
    GeocodingResponse res = GoogleGeocodingAPI.getGeocodingResponse('test');
    System.assertEquals('OK', res.status);
    System.assertEquals(40.730610, res.results[0].geometry.location.lat);
    System.assertEquals(-73.935242, res.results[0].geometry.location.lng);

    // レスポンスにエラーが含まれることを検証
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT += 'error';
    res = GoogleGeocodingAPI.getGeocodingResponse('test');
    System.assertEquals('ERROR', res.status);
    System.assertEquals('Test error message', res.error_message);

    // レスポンスが空の場合を検証
    GoogleGeocodingAPI.GEOCODING_API_ENDPOINT = GoogleGeocodingAPI.GEOCODING_API_ENDPOINT.replace('error', 'empty');
    res = GoogleGeocodingAPI.getGeocodingResponse('test');
    System.assertEquals('OK', res.status);
    System.assertEquals(0, res.results.size());
  }
}

以上が、すべてのテストケースを実装したテストコードの例です。このテストコードを使用して、トリガーが正しく動作するかどうかを検証することができます。

A2.感想

おかしなところは散見されるが、もう修正する体力は残されていない。

しかし、これはいいんじゃないか。
テストケースの網羅は面倒だし、漏れが出がちなので、ある程度のコードでも作ってもらえるとだいぶ助かるぞ。

終わりに

これは使えるぞ。

昨日のコードは一見よさそうに見えるが、けっこう間違いもあったので、テストコードの叩き台を書いてもらうのがいいかもしれませんね。
でも、ちなみに昨日のコードを渡して「間違いを指摘してください」とChatGPTに指示するといろいろ間違いを指摘してくれます。自分の書いたコードなのにねw でも頑なに間違いを認めないこともあるので、まだ信用しすぎないようにしましょう。

それと、途中で切れて「続き」ってやると高確率で以下のようにコードブロックが壊れるので記事を書くのが辛かったです。
image.png

8
3
0

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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?