LINE株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。 LINEヤフー Tech Blog

Blog


LINE STOREにおけるテスト自動化の取り組み

この記事はLINE Advent Calendar 2017の7日目の記事です。

こんにちは、LINE FukuokaでLINE STOREのサーバーサイド開発をしているHiraiです。本日は、LINE STOREにおけるテスト自動化の取り組みを紹介したいと思います。

LINE STOREとは

LINE STOREというサービスをまだご存じでない方もいらっしゃると思いますので、まずはその紹介から。

LINE STOREは、LINEのスタンプや着せかえ、ゲームやファミリーサービス(LINEプレイ、LINEマンガ、LINE占い、LINE LIVEなど)の通貨を購入できるウェブのサービスです。購入したスタンプや着せかえをLINEの友だちにプレゼントできたり、クレジットカード以外(キャリア決済、LINEプリペイドカードなど)の決済手段を使用できたりなど、LINE STORE独自の機能もあります。

今年になって、公式アカウントを友だち追加したり無料スタンプが取得したりできる機能も追加されました。日々コンテンツが充実してきていますので、ぜひ一度アクセスしてみてください。

LINE STORE

テスト自動化に取り組む理由

JUnitやSeleniumなどのライブラリが充実し、テスト自動化が一般的になりつつある今、テスト自動化に取り組む理由を説明する必要はないかもしれません。しかし、特に理由を挙げるとすれば、それはLINE STOREのリリースサイクルにあります。

私が所属するチームでは、毎週サービスリリースを実施しています。

サービスの品質を確保するためには、新規開発した機能のテストとともに、既存の機能に影響がないかのテスト、つまりリグレッションテストが重要ですよね。毎週のリリースを達成するためには、このリグレッションテストを少なくとも数日で完了させなければなりません。

一度だけのリグレッションテストであれば、手動テストのみで対応できると思います。しかし、これを毎週繰り返すとなると、その人的コストは膨大になっていきます。毎週の繰り返しの作業の中、テスターが明らかなバグを見逃してしまう場合もあるかもしれません。また、テスターの欠員などの異例な理由で、毎週のリグレッションテストが達成できない場合もあるかもしれません。

LINE STOREではこれらの懸念を解消し、人的コストの削減、品質の確保、テストサイクルの短縮化、そして安定した毎週のリリースを達成するために、積極的にテストの自動化に取り組んでいます。

テスト自動化の種類

次に、取り組んでいる自動化テストの種類を説明します。

「テスト自動化のピラミッド」というモデルがあります(テスト自動化の話では頻繁にでてくる図なので、見たことがある方も多いと思います)。

テストは「ユニット(単体)」「インテグレーション(統合)」そして「UI」の3つの階層から構成されていて、下の層の「ユニット(単体)」「インテグレーション(統合)」は自動化の開発コストが低くROI(投資収益率)が高い一方で、上の層の「UI」は開発や保守のコストが高くROIが低いので、より下の層から自動化を優先した方がよい。というものです。

この考え方に基づいて、LINE STOREでは「ユニット(単体)」と「インテグレーション(統合)」のテストはすべて自動化しています。これらの層のテストプログラムは、開発エンジニアが、漏れのないよう機能の開発と同じタイミングで作成します。「UI」のテストプログラムの開発についてはQAチームの自動化エンジニアが担当し、QAエンジニアが作成したテストケースを自動化しています。そして自動化できない部分についてのみ、テスターが手動でテストします。

テスト自動化のピラミッド

それでは次に、それぞれの自動化テストをどのように行っているのかを説明します。

ユニットテスト

ユニットテストは、個々の機能の挙動を確認するテストです。

LINE STOREの開発言語はJavaなので、基本的にメソッド単位でテストプログラムを開発しています。テスト対象のメソッドの挙動のみを確認するために、テストの対象でないクラスには「モック」を使用します。

次のコードはその一例です。Mockitoを使って、モックを作成します。ちなみに、結果の評価にはAssertJを使っています。

@Rule
public MockitoRule rule = MockitoJUnit.rule();

// create mock object
@Mock
private ShopStorage shopStorage;

// test target
private ShopService target;

@Before
public void setUp() {
    target = new ShopService(shopStorage);
}

@Test
public void testGetAvailableSummaryList(){
    List<ShopModel> shopModelList = ImmutableList.of(shopModel("tmtm"), shopModel("ranger"));

    // if specific parameters are set, return "shopModelList"
    when(shopStorage.getShops(ShopCategoryType.GAME, "JP").then(shopModelList));

    // test & verify result
    assertThat(target.getAvailableSummaryList(JP_USER, ShopCategoryType.GAME).size()).isEqualTo(2);
}

開発したテストプログラムは、JenkinsのGitHub Pull Request Builder Pluginを使って、プルリクエスト作成時に実行しています。またプルリクエストのコードレビューで、機能に対するユニットテストが漏れなく開発できているかをチーム内で確認しています。

インテグレーションテスト

インテグレーションテストは、それぞれの機能を組み合わせたテストです。サービスを構成するストレージ、データベース、そして外部APIなど諸々のモジュールを組み合わせて、正しいデータが取得できるかを確認します。

次のコードはその一例です。Spring FrameworkMockMvcを使って、Controllerクラスが正しいデータを出力しているかを確認しています。

@Inject
WebApplicationContext wac;

MockMvc mockMvc;

@Before
public void setUp() {
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}

@Test
public void testAuthorList() throws Exception {
    String url = "/stickershop/author/31/en";
    mockMvc.perform(get(url))
           // check http status code
           .andExpect(status().isOk())
           // check attributes of ModelAndView
           .andExpect(model().attribute("listType", ListType.AUTHOR_LIST))
           // check meta tag values in html
           .andExpect(xpath("/html/head/meta[@property='og:description']/@content").string(containsString("LINE")));
}

LINE STOREは、スタンプ、着せかえ、ゲームなど、いろいろな外部APIと通信するので、その疎通テストができるインテグレーションテストはとても重要です。しかしユニットテストと同じように、GitHubのプルリクエスト作成時にテストを実行してしまうと、外部APIの停止などでマージなどの作業を妨げる可能性があります。

そこでインテグレーションテストではPull Request Builder Pluginは使わず、Jenkinsで定期的にテストプログラムを実行しています。テストの結果はチャットツールでリアルタイムに通知して、開発エンジニアがその失敗を即座に検知できるようにしています。

UIテスト

UIテストは、サービスのエンドツーエンドのテストです。前にも述べたとおり、このUIテストのテストプログラムの開発はLINE Fukuoka QAチームの自動化支援パートが担当しています。

次のコードは、そのテストプログラムの一例です。Seleniumを使って画面が正しく表示されるかと、次の画面に正しく遷移できるかを確認しています。QAエンジニアが作成したテストケースが元になっているので、細かい表示の確認なども行っています。

@Test
public void test001() throws Exception {
    a.testCaseStart("ログイン状態、ログアウト状態の表示確認").category("ヘッダー");

    a.stepStart("LINE STOREを開く。欲しいものリストチュートリアルが表示されていたら消す");
    TopPage toppage = open("/home/ja", TopPage.class)
            .closeModalWindowIfExists();
    a.stepFinish();

    a.stepStart("ログインする");
    toppage.ifLoggedOutThenLogin(store.getMainAccount().getEmail(),
                                 store.getMainAccount().getPass(),
                                 setting.getStage());
    a.stepFinish();

    a.stepStart("アカウント名:「" + store.getMainAccount().getName() + "」が表示されていること");
    a.assertThat("アカウント名", toppage.getAccountNameText(), is(store.getMainAccount().getName()));
    a.stepFinish();

    a.stepStart("ログアウトボタンを押すと正常にログアウトし、ユーザー名の表示はなくなること");
    toppage.ifLoggedInThenLogout();
    a.assertThat("アカウント名が表示されていないこと", toppage.hasAccountNameLink(), is(false));
    a.stepFinish();
}

自動化テストで使用しているウェブブラウザーはChromeとFirefoxの2種類で、デバイスはパソコンとスマートフォンを対象としています。スマートフォンでのテストにはエミュレーターを使わないで、Appniumを使って実機で確認しています。

次の画面は、自動化テストの結果画面です。

テストケースとその成功と失敗の他に、自動化テスト実行中のブラウザーのキャプチャー画像とサーバーのトレースログ(デバッグログ)を表示しています。自動化テストに失敗した場合、キャプチャー画像から即座にその状況を把握できます。またサーバーのログがあれば、開発エンジニアはテスト失敗時刻のログを追う必要がなく、効率的に調査できます。

LINE STORE

次に、自動化テストのプラットフォームについて説明します。

パソコン向けの自動化テストに限ってですが、Zaleniumというツールを使用しています。ZaleniumはSelenium Gridのエクステンションで、次のような特長があります。

  • テスト実行のリクエストを受けると、ZaleniumはオンデマンドでDockerコンテナを起動します。あらかじめテスト環境を確保しておく必要がないので、簡単に分散化、並列化を実現できます。

  • テストに関係するライブラリやモジュールはDockerコンテナ内に全てパッケージングされます。これにより、常に安定したバージョンのライブラリ、モジュールでテストを実行できます。

  • 追加の作業なしで、テストのビデオ録画機能や実行中のテストのライブプレビュー機能を使用できます。

Zaleniumを導入したのは、自動化テストのテストケースの充実に伴い、自動化テストが長時間化してきたこと、またテストに関係するライブラリをバージョンアップした時に、テストプログラムが壊れやすいことを解決するためでした。

次の図は、自動化テスト実行のフロー図です。

  1. Jenkins Slaveの定期ジョブで、Seleniumのテストを実行します。テストを実行すると、Zalenium上のSelenium Grid Server(Hub)に対して、テスト実行のリクエストが発行されます。

  2. Hubはテスト実行のリクエストを受けると、Dockerコンテナ(Node)をオンデマンドで起動します。

  3. Node内で、テストが実行されます。

  4. スマートフォン向けのテストは、実機デバイスが接続された端末内ですべて実行されます。

  5. テストの実行結果は、リアルタイムにテスト結果集計サーバーに送信され蓄積されます。

Zaleniumは最近導入したばかりなので、構成やフローの最適化についてはまだ調整中の状況です。例えば、Zaleniumのビデオ録画機能はまだ活用できていないのですが、テスト結果集計の部分など、この機能で最適化できるかもしれませんね。

Automation Test Platform

障害検知

最後に、テスト自動化の仕組みがテストの他の場面で用いられているケースを紹介したいと思います。

LINEには障害検知を専門に行うチームがあって、24時間365日LINE全体のサービスをエンドツーエンドで監視しています。UIテストの自動化の仕組みを、この監視の初期検知ツールに使用しています。監視対象は、LINE STOREに限らないLINE全体のサービスです。

ここでの確認は、UIテストで実施しているような細かい確認ではなく、LINE STOREでいえば、ログインして、アイテムの購入画面を開けるか?のような粒度が大きく、かつサービスにとって重要な機能のみを確認しています。また、確認結果はチャットツールでリアルタイムに障害検知チームのオペレーターに共有されます。テストの失敗が通知されると、オペレーターが手動で確認した後、各サービスの担当者に通知するフローになっています。

初期検知を自動化することで、障害検知チームのオペレーターは担当のサービスの画面を常に監視している必要がなくなります。チャットツールに通知される確認結果を監視しておけば良いので、オペレーター1人当たりの監視可能なサービスが増え、人的リソースを削減できます。また、オペレーターの操作に依存しないので、1分間隔などの定期的で、かつ安定した監視を実現できます。

まとめ

以上、LINE STOREのテスト自動化の取り組みを紹介しました。

ユニットテストとインテグレーションテストは自動化できているが、UIテストについてはコスト的に自動化できていない、という方もいるかもしれません。しかし、テスト自動化を取り巻くプラットフォームは日々進化していて、そのハードルは決して高いものではなくなってきています。

すべてのUIテストを自動化することは難しいですが、まずは大きな粒度、例えば重要な画面の疎通確認から始めてみたり、障害の初期検知ツールとして使ってみたりして、テスト自動化の幅を少しずつ広げてみてはいかがでしょうか。

明日はha1fさんによる「ブラウザでアニメーションスタンプ画像を深く読み込む」についての記事です。お楽しみに!