Cypressの拡張メソッドを使ってテストコードを判り易く!
2019-07-01
azblob://2022/11/11/eyecatch/2019-07-01-cypress-extension-method-000.jpg

こんにちは。最近TypeScriptで真面目に日本語プログラミングに取り組んでいる冨本です。
今回はE2Eテストなどで使われるCypressの拡張メソッドという機能を紹介していこうと思います。

Cypressとは

この記事を閲覧している時点でご存知だとは思いますが、CypressとはWeb UI の自動テストツールです。JavaScriptやTypeScriptで記述することができます。
同じWeb UIのテストツールではSeleniumが有名ですが、私は使ったことがありません。

拡張メソッドを使うメリット

拡張メソッドを使うメリットは主に2つあると私は考えています。

1つ目は再利用性の向上です。E2Eのテストコードを書く上で、同じようなAssertionを複数回記述することがあるかと思います。その際にそのAssertionの処理を拡張メソッドとして登録しておくことで、毎回記述することなく、そのメソッドを呼び出すだけで簡単に処理を行うことができます。

2つ目はテストコードの可読性の向上です。E2Eのテストコードは様々な人が見るコードです。 テストコードは誰が見ても一目でどのようなテストを行っているのかが判るべきです。
例えば、ページトップに移動するボタンのテストをするとして、ページトップに移動しているかどうかの判定をする際、テストコードに

TypeScript cy.window().then($window => {
    expect($window.scrollY).to.be.closeTo(0, 0);
  });

と書くのと、上記の処理を拡張メソッド.isPageTopとして登録しておき、

TypeScriptcy.isPageTop();

と記述するのでは判り易さは段違いだと思います。
このように拡張メソッドを使うことで自分も記述しやすく、他人からも判り易いテストコードを書くことができます。

次に実際に拡張メソッドの追加を行っていきます。

実際に拡張メソッドを追加してみる

Cypressのインストールなどは公式のチュートリアルがとても充実していますので、そちらを参考にしてください。ここでは割愛させていただきます。

まずデフォルトコマンドのみでコードを書いてみる

そうですね、では「Googleマップにアクセスして"近くの駅"と検索。その検索結果が正しいか」のテストをします。デフォルトコマンドのみで書いてみると以下のようなコードになりました。

describe('Googleマップの検索結果が表示されているかのテスト', () => {

  before('login', () => {
    //Googleマップにアクセス
    cy.visit('https://www.google.com/maps/@35.6406599,139.7288496,13z?hl=ja')
  });

  it('result', () => {
    //テストデータ
    const stationName = [' 表参道駅  ',' 渋谷駅  ',' 広尾駅  ',' 恵比寿駅  ',' 代官山駅  ',' 明治神宮前〈原宿〉駅  ',' 外苑前駅  ',' 乃木坂駅  ',' 神泉駅  ',' 原宿駅  '];

    //検索ワードの入力
    cy.get('input')
      .eq(0)
      .type('近くの駅');

    //検索ボタンのクリック
    cy.get('.searchbox-searchbutton-container')
      .click();

    //検索結果が表示されるまで待つ
    cy.pause();

    //検索結果の表示が正しいかどうかの判定
    cy.get('.section-result-content')
      .find('.section-result-title')
      .each(($el, index) => {
        expect(stationName[index]).eq($el.context.textContent);        
      });
  });
});

ぱっと見てどういったテストをしているのか判りにくいですね。
更に言うと今回はテスト項目が1つですが、実際にはテスト項目が1つなんてことはあまりありません。
実際にはテスト項目がいくつもあります。シナリオテストなどでは別のシナリオで同じAssertionを書くことも多くあります。そうなるとこの長々としたAssertionを毎回書くとなると見栄えも悪いです。

そこで拡張メソッドの出番です。

拡張メソッドの追加

先程のAssertionの部分を拡張メソッドに追加します。拡張メソッドを記述する場所ですが cypress/support/commands.jsファイルに追加していきます。
下記のように拡張メソッドを追加します。

Cypress.Commands.add("resultsIsCorrect", (stationNames) => { 
  cy.get('.section-result-content')
      .find('.section-result-title')
      .each(($el, index) => {
        return expect(stationNames[index]).eq($el.context.textContent);        
      });
})

このコマンドはメソッド名がresultsIsCorrectで引数にstationNamesの配列をもらっています。

コマンドを追加したのでテストコードも変更していきます。

describe('googlemapの検索結果が表示されているかのテスト', () => {

  before('login', () => {
    //Googlemapにアクセス
    cy.visit('https://www.google.com/maps/@35.6406599,139.7288496,13z?hl=ja')
  });

  it('result', () => {
    //テストデータ
    const stationNames = [' 表参道駅  ',' 渋谷駅  ',' 広尾駅  ',' 恵比寿駅  ',' 代官山駅  ',' 明治神宮前〈原宿〉駅  ',' 外苑前駅  ',' 乃木坂駅  ',' 神泉駅  ',' 原宿駅  '];

    //検索ワードの入力
    cy.get('input')
      .eq(0)
      .type('近くの駅');

    //検索ボタンのクリック
    cy.get('.searchbox-searchbutton-container')
      .click();

    //検索結果が表示されるまで待つ
    cy.pause();

    //検索結果の表示が正しいかどうかの判定
    cy.resultsIsCorrect(stationNames);
  });
});

どうでしょうか。先程の長々しいAssertionが1行にまとまり、メソッド名からテストの内容が判り易くなったと思います。

実際に動かしてみる

追加した拡張メソッドが動作するかどう確かめていきます。Cypressを実行するだけです。

Cypressを実行し、拡張メソッドの動作を確認

きちんと動いていますね。この拡張メソッドは他のテストコードのファイルでも使用することができます。

まとめ

拡張メソッド、いかがでしたでしょうか。テストコードが自分も書き易くなり他人も判り易くなる、まさに一石二鳥のとても良い機能だと思います。
ぜひ皆さんも積極的に利用していきましょう!