Salesforce技術ブログ(開発者コンソールの匿名実行Apexツールでテストカバー率一覧を取得してみる)

Salesforce技術ブログ(開発者コンソールの匿名実行Apexツールでテストカバー率一覧を取得してみる)

Salesforce技術ブログ(開発者コンソールの匿名実行Apexツールでテストカバー率一覧を取得してみる)

こんにちは!ドライ市来です!

皆さんビールは何派でしょうか?
私はもちろんアサヒのスーパードライ派です!(クリアアサヒではありません!)

開発者コンソールの匿名実行Apexツールは活用してますか?

そもそも、開発者コンソールの匿名実行Apexツールとはなんだ!?って方、
下記ヘルプを参照してください。m(_ _)m
https://help.salesforce.com/apex/HTViewHelpDoc?id=code_dev_console_execute_anonymous.htm&language=ja

本題ですが、
このツールを使っていろいろとやってみようと思います。

その第1弾として、クラスとトリガのテストカバー率を取得するApexコードを書いてみました!
※テストクラスの実行はしません。現在のカバー率を取得します。

まず、なぜこのコードを書いたかというと、ご存知の方もいらっしゃると思いますが、
Winter'14のリリースでApexクラス一覧からクラスのカバー率の列が削除されましたよね。

リリースノート抜粋_コードカバー率について
https://resources.docs.salesforce.com/186/latest/ja-jp/sfdc/pdf/salesforce_winter14_release_notes.pdf
↑306ページ

なんで削除されたんだ?と思った方も多いのではないでしょうか。(たぶん...)

ということで、どうにか一覧表として取得できないかなぁと思い、書いてみました。

開発者コンソールの[Debug]->[Open Execute Anonymous Window]で匿名実行Apexツールを開き、
下記コードを貼り付けて[Execute]ボタンをクリックすると、
ドキュメントの「私の個人ドキュメント」フォルダにクラスとトリガのカバー率一覧のファイルが
XMLスプレッドシート形式で保存されます!!

// ClassとTriggerのカバー率を取得するクエリ
String queryStr = 'Select+id,ApexClassorTriggerId,ApexClassorTrigger.Name,NumLinesCovered,NumLinesUncovered+from+ApexCodeCoverageAggregate+order+by+ApexClassorTrigger.Name';

HttpRequest req = new HttpRequest();
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm()+'/services/data/v37.0/tooling/query/?q='+queryStr);
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());
req.setHeader('Content-Type', 'application/json');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);

Map<String, Object> objMap = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());

List<Object> objList = (List<Object>)objMap.get('records');

// クラス一覧ワークシート
String clsListWS = '';
// トリガ一覧ワークシート
String trgListWS = '';

clsListWS += '<Worksheet ss:Name="クラス一覧">\r\n' +
               '<Table ss:ExpandedColumnCount="7" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">\r\n' +
                 '<Column ss:AutoFitWidth="0" ss:Width="21.75"/>\r\n' +
                   '<Row>\r\n' +
                     '<Cell><Data ss:Type="String">クラス一覧</Data></Cell>\r\n' +
                   '</Row>\r\n' +
                   '<Row>\r\n' +
                     '<Cell><Data ss:Type="String">No</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Id</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Name</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">NumLinesCovered</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">NumLinesUncovered</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">TotalNumLines</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Coverage</Data></Cell>\r\n' +
                   '</Row>\r\n';


trgListWS += '<Worksheet ss:Name="トリガ一覧">\r\n' +
               '<Table ss:ExpandedColumnCount="7" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">\r\n' +
                 '<Column ss:AutoFitWidth="0" ss:Width="21.75"/>\r\n' +
                   '<Row>\r\n' +
                     '<Cell><Data ss:Type="String">トリガ一覧</Data></Cell>\r\n' +
                   '</Row>\r\n' +
                   '<Row>\r\n' +
                     '<Cell><Data ss:Type="String">No</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Id</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Name</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">NumLinesCovered</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">NumLinesUncovered</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">TotalNumLines</Data></Cell>\r\n' +
                     '<Cell><Data ss:Type="String">Coverage</Data></Cell>\r\n' +
                   '</Row>\r\n';

Integer clsCnt = 1;
Integer trgCnt = 1;
for(Object obj : objList){
    Map<String, Object> codeCoverageObj = (Map<String, Object>)obj;
    Map<String, Object> clsOrTrgObj = (Map<String, Object>)codeCoverageObj.get('ApexClassOrTrigger');
    
    Double numLinesCovered = Double.valueOf(codeCoverageObj.get('NumLinesCovered'));
    Double totalNumLines = Double.valueOf(codeCoverageObj.get('NumLinesCovered')) + Double.valueOf(codeCoverageObj.get('NumLinesUncovered'));
    
    // クラス単体のカバー率を計算
    Decimal coverage = 0;
    if(numLinesCovered != 0){
        coverage = (numLinesCovered / totalNumLines) * 100;
        coverage = coverage.setScale(0, System.RoundingMode.HALF_UP);
    }
    
    String clsOrTrgId = String.valueOf(codeCoverageObj.get('ApexClassOrTriggerId'));
    
    // ClassのIdのPrefix
    if(clsOrTrgId.startsWith('01p')){
        clsListWS += '<Row>\r\n' +
                       '<Cell><Data ss:Type="Number">' + clsCnt + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + clsOrTrgId + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + String.valueOf(clsOrTrgObj.get('Name')) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(numLinesCovered) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(codeCoverageObj.get('NumLinesUncovered')) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(totalNumLines) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + coverage + '%' + '</Data></Cell>\r\n' +
                     '</Row>\r\n';
        
        clsCnt++;
    }
    // TriggerのIdのPrefix
    else if(clsOrTrgId.startsWith('01q')){
        trgListWS += '<Row>\r\n' +
                       '<Cell><Data ss:Type="Number">' + trgCnt + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + clsOrTrgId + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + String.valueOf(clsOrTrgObj.get('Name')) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(numLinesCovered) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(codeCoverageObj.get('NumLinesUncovered')) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="Number">' + String.valueOf(totalNumLines) + '</Data></Cell>\r\n' +
                       '<Cell><Data ss:Type="String">' + coverage + '%' + '</Data></Cell>\r\n' +
                     '</Row>\r\n';
        
        trgCnt++;
    }
}

clsListWS += '</Table>\r\n' +
           '</Worksheet>\r\n';

trgListWS += '</Table>\r\n' +
           '</Worksheet>\r\n';

String workBook = '<?xml version="1.0"?>\r\n' +
                  '<?mso-application progid="Excel.Sheet"?>\r\n' +
                  '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">\r\n' +
                  clsListWS +
                  trgListWS +
                  '</Workbook>';

Document doc = new Document();
String timeStamp = DateTime.now().format('yyyyMMddHHmmss');
doc.Name = 'TestCoverage_' + timeStamp + '.xml';
doc.DeveloperName = 'TestCoverage_' + timeStamp + '_xml';
doc.FolderId = UserInfo.getUserId();
doc.Body = Blob.valueOf(workBook);
doc.type = 'xml';
insert doc;

※このコードを実行前に「リモートサイトの設定」が必要となります。
[設定]->[セキュリティのコントロール]->[リモートサイトの設定]で下記URLを設定しておいてください。
https://【ログイン環境のインスタンス】.salesforce.com


出力されるファイルはこんな感じです↓
カバー率_出力ファイル

ぜひ活用してみてください!