[Apex] 指定サイズのBlobデータを作成する

テストコードでファイル添付が可能なオブジェクト（添付ファイルやFeedItemなど）のテストデータを作成する時、指定サイズのファイルを添付したい… といった場合などに使えるネタです。

##指定サイズのBlobデータを作成する方法

指定のバイト数分のString値を作成し、Blob型に変換します。

以下のサンプルでは、1MBのBlobデータを作成しています。
※本記事では1KB=1,000byteとして計算しています。[^1]

[^1]: Salesforce上で表示されるファイルサイズの表記は1KB=1024byteです。

```java:Apex
Integer byteSize = (Integer)Math.pow(10, 6); // 1MBをbyteに換算 -> 1,000,000
String s = ('0').repeat(byteSize);           // 1バイト文字を繰り返し、1MB分の文字列を生成
Blob myBlob = Blob.valueOf(s);

System.debug(myBlob.size());                 // -> 1,000,000
````

###作成可能なサイズの上限
以下のエラーが発生しないようにサイズを抑える必要があります。

- String値を生成する際、String型で扱える文字列の長さの上限（6,000,000）を超えるとLimitExceptionが発生してしまいます。

```java:
/* 7MB分の文字列を作りたい */
Integer byteSize = (Integer)Math.pow(10, 6) * 7;  // -> 7,000,000
String s = ('0').repeat(byteSize);                 // System.LimitException: String is too long.
```

- Blobデータを作成すると、その分ヒープサイズを消費します。Apexにはヒープサイズのガバナ制限[^2]があり、上限を超えるとガバナ制限エラーが発生してしまいます。

以下のようにコードを変更すると *String is too long* エラーを回避できますが、ヒープサイズのガバナ制限に違反しているのでエラーが発生します。

```java:
/* 7MB分の文字列を作りたい */
Integer byteSize = (Integer)Math.pow(10, 6);  // 1MBをbyteに換算 -> 1,000,000
Blob myBlob = Blob.valueOf('');               // 空のBlobを作成
for(Integer i = 0; i < 7; i++){
  String s = ('0').repeat(byteSize);
  myBlob = Blob.valueOf(myBlob.toString() + Blob.valueOf(s).toString());  // -> System.LimitException: Apex heap size too large: 7001380
}
```

[^2]: 同期Apexは6MB、非同期Apexは12MB（Winter'16時点。参考：[Apexガバナ制限](https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apexgov.htm)）

##テストコードでの利用例
Apexトリガで1MBを超える添付ファイルの登録を禁止する処理に対して、テストコードは以下のように書くことができます。

```java:添付ファイルのトリガ
trigger AttachmentTrigger on Attachment (before insert) {
  for(Attachment att:Trigger.new){
    // 1MBを超えるファイルの添付を禁止
    if(att.Body.size() > (Integer)Math.pow(10, 6)){
      att.BodyLength.addError('1MBを超えるファイルは添付できません');
    }
  }
}
```
```java:テストメソッド
@isTest static void fileSizeExceededErrorTest() {
  // 添付ファイルの親レコードを作成
  Account acc = new Account(Name = 'Test Account');
  insert acc;

  Test.startTest();
  // 添付ファイルのBodyを作成
  Integer byteSize = (Integer)Math.pow(10, 6) + 1;  // 1MB + 1byte
  String bodyString = ('0').repeat(byteSize);
  // 添付ファイルレコードを作成
  Attachment att = new Attachment(
    Name = 'TestData.txt',
    Body = Blob.valueOf(bodyString),
    ParentId = acc.Id
  );

  String errorMsg;
  try{
    insert att;
  }catch(DmlException e){
    errorMsg = e.getDmlMessage(0);
  }
  Test.stopTest();

  System.assertEquals('1MBを超えるファイルは添付できません', errorMsg);  // OK

}
```

しかしファイルサイズの上限を10MBに引き上げたい場合、同じサイズのBlobデータを作ろうとすると前述のガバナ制限エラーが発生してしまいます。
これではテストケースの実行ができないため、Test#isRunningメソッドを使用してテスト実行時だけエラー条件を変えるという手があります。

```java:添付ファイルのトリガ
trigger AttachmentTrigger on Attachment (before insert) {
  for(Attachment att:Trigger.new){
    // ファイルサイズをチェックする
    // 本番実行時は10MB、テスト実行時は10KBを超えていたらエラー
    //  -> テストコードでは10KBのBlobデータを作れば良い
    Integer bodySize = att.Body.size();
    Integer fileSizeLimit = (Integer)Math.pow(10, 6) * 10;
    Integer fileSizeLimitForTest = (Integer)Math.pow(10, 3) * 10;
    if(
      (!Test.isRunningTest() && bodySize > fileSizeLimit) || 
      (Test.isRunningTest() && bodySize > fileSizeLimitForTest)
    ){
        att.addError('10MBを超えるファイルは添付できません');
    }
  }
}
```

###考慮事項
Salesforceプラットフォームはマルチテナントで動作しているため、テストのコードカバー率を上げるためだけに大量にヒープを消費することは避けるべきです。
ヒープサイズの上限近くまで大きなBlobデータを作るようなことはせず、上記サンプルのようなサイズを抑える手段を検討すると良いでしょう。

##参考リンク
- [String Character Limits (creating large blob attachment). - Salesforce Developer Community](https://developer.salesforce.com/forums/?id=906F00000008w9hIAA)