DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

iOSDC Japan 2019でリジェクトリスクを低減する取り組みについて発表しました

SWETの加瀬(@Kesin11)です。

先日開催されたiOSDC 2019にて登壇する機会を頂き、「iOSアプリのリジェクトリスクを早期に発見するための取り組み」という発表をしました。

当日は時間の都合上、紹介したツール(以降、AppChecker)がipaをどのように解析し、どのようにチェックを行っているかというロジックの要点だけの紹介しかできず、コードを示した解説まではできませんでした。

AppCheckerは社内の要件やフローに特化した作りとなっているために、残念ながら今のところOSSにする予定はありません。ですが、自分たちと同様のチェッカーを実装したい方が参考にできるように、どのような実装しているのか簡単なサンプルコードで紹介したいと思います。

実装コードの紹介

以下のコードではipa内のInfo.plistからXcodeとiOS SDKのバージョンのチェックを行っています。

#!/usr/bin/env ruby

# app_checker_light.rb
require 'fastlane_core/ipa_file_analyser'
require 'fastlane_core/ui/ui'

# 各チェッククラスのベースクラス
class Checker
  class << self
    def check(info_plist)
      raise 'Not inplmeneted error'
    end

    # 要求されている下限バージョンの定義
    def config
      {
        'DTXcode' => '10.1.0',
        'DTPlatformVersion' => '12.1.0',
      }
    end
  end
end

# Xcodeの必須バージョンをチェックするクラス
class XcodeVersionChecker < Checker
  class << self
    def check(info_plist)
      version = info_plist['DTXcode'].to_i
      # Xcodeのバージョンが10.1の場合はDTXcode: '1010'となる
      # これをmajor, minor, patchに分解する
      major = (version / 100)
      minor = ((version % 100) / 10)
      patch = (version - (major * 100) - (minor * 10))

      # バージョンとして比較できるようにGem::Versionのインスタンス作成
      actual_version = Gem::Version.create([major, minor, patch].join('.'))
      expect_version = Gem::Version.create(config['DTXcode'])

      if actual_version < expect_version
        FastlaneCore::UI.error("必要なXcodeバージョン: #{expect_version}, 実際のバージョン: #{actual_version}")
      end
    end
  end
end

# iOS SDKの必須バージョンをチェックするクラス
class PlatformVersionChecker < Checker
  class << self
    def check(info_plist)
      version = info_plist['DTPlatformVersion'].to_i

      actual_version = Gem::Version.create(version)
      expect_version = Gem::Version.create(config['DTPlatformVersion'])

      if actual_version < expect_version
        FastlaneCore::UI.error("必要なiOS SDKバージョン: #{expect_version}, 実際のバージョン: #{actual_version}")
      end
    end
  end
end

ipa_path = ARGV[0]
info_plist = FastlaneCore::IpaFileAnalyser.fetch_info_plist_file(ipa_path)

# 新しい種類のCheckerを追加したときはこの配列に追加する
checker_classes = [
  XcodeVersionChecker,
  PlatformVersionChecker
]

# 各クラスのチェックを順番に実行
checker_classes.each { |klass| klass.check(info_plist) }

実行方法

$ bundle exec app_checker_light.rb YOUR_APP.ipa

上記のコードは非常に簡易的なクラス設計となっていますが、実際のAppCheckerは多くのチェック項目を扱え、今後の拡張性を考慮した重厚なクラス設計となっています。

実際のAppCheckerは、このコア部分を呼び出すラッパーとしてThorでコマンドラインから呼び出せるようにした"スタンドアローン版"と、Fastlaneから呼び出せるようにした"Fastlaneプラグイン版"の2種類を提供しています。

Fastlaneのプラグインを作成するのは初めてでしたが、 fastlane new_plugin [plugin_name] というコマンドでひな形となるファイル一式を生成できるため、意外に予想していたよりも簡単でした。もし既に何らかのRubyの便利スクリプトをお持ちであれば、こちらのドキュメントを参考にしてFastlaneプラグインにしてみるのも良いかもしれません。

テスト、CI/CD

AppCheckerは社内の多くのチームのビルドパイプラインに組み込んでもらうことになります。そのため社内ツールといえども非常に高い品質が求められ、それぞれのチェック処理は必ずユニットテストを書きながら開発しました。

さらに、pull-requestによって走るCIではそれらのユニットテストに加えて、Bitriseで実際にサンプルアプリからビルドしたipaに対してAppCheckerを実行してエラーが発生しないことを確認するテストも実行しています。

この確認をする理由は、AppCheckerがxcrunなどXcodeと関係する外部ツールを内部的に使用しているためです。Xcodeのバージョンが上がった際にそれらのツールが期待どおりに動作しなくなる可能性が存在するため、最新のXcode環境を使用できるBitriseを活用して常に最新のXcodeの環境でエラーなく動作することを確認しながら開発しています。

このあたりのAppChecker本体のCI/CDについてはiOSDCでは残念ながら発表時間の都合上お話しできませんでしたが、実はその前の9/3に開催されたBitrise User Group Meetup #2にて発表していました。そのときのスライドはこちらになります。

ガイドラインを追うために

iOSDCの発表中でもお伝えしましたが、App Storeのガイドラインは今後もアップデートされ続けていきます。こちらのAppleのデベロッパー向けニュースは、朝刊を読むのと同じように毎日チェックしましょう。

日本語版は本家の英語よりも数日遅れて発信されるため、できれば英語版のRSSをチームのSlackに流してチーム全員でチェックするのが良いでしょう。

App Store Review Guidelinesのurlはこちらです。

現在のところ1ページに全て収まっています。現行のガイドライン本文を保存しておき、次回の更新時にdiffを取ることでどの部分が更新されたのかハッキリと分かるでしょう。

終わりに

iOSDCで発表したAppCheckerについて補足をさせて頂きました。

当初、SWETで開発がはじまったAppCheckerですが、現在はQAグループ内の自動化を推進しているチームに開発・運用を移管し、アプリがよりリリースしやすい形となるような体制にしています。

SWETではテスト自動化の普及に加え、こうした全社的にコストを下げる仕組みの提案・開発なども行っています。iOSに限らず、複数の技術領域でエンジニアを募集しています。

ご興味を持たれた方はぜひご応募ください。