yu-tarrrrの日記

完全に個人の趣味でその時々書きたいことを書く

gaugeとTypeScriptでテストをいい感じにする

はじめに

  • 今回はgaugeというテストライブラリを使い、web driverを使ったテストを書いていこうと思います

そもそもgaugeとは

gauge.org

  • マークダウン形式でテストシナリオを定義でき、実行ファイルとシナリオを定義しているファイルを分離できるというもの
  • メリットとしては、テストを自然言語でかつマークダウンで書けることで、単にテストを書くよりも可読性が大幅に上がるという点があります。

はじめる

  • OSX前提で書きます
  • ターミナルにHomebrewが入ってる前提で進めます
  • nodeとnpmも入ってる前提で進めます

gaugeをインストールする

$ brew install gauge
# gaugeのバージョンが見られればOK
$ gauge -v
Gauge version: 1.0.9
Plugins
-------
html-report (4.0.8)
java (0.7.3)
screenshot (0.0.1)
ts (0.1.0)
xml-report (0.2.2)
  • VSCode のExtensionがあるのでそれを入れておく

marketplace.visualstudio.com

# 用意されているテンプレートを確認しておく
$ gauge init -t
dotnet
java
java_gradle
java_maven
java_maven_selenium
js
js_simple
python
python_selenium
ruby
ruby_selenium
ts
csharp

# 今回はtsで作成する
$ gauge init ts
Downloading ts.zip
Copying Gauge template ts to current directory ...

> protobufjs@6.10.1 postinstall /Users/yuta.aikawa/sources/gauge/sample/node_modules/protobufjs
> node scripts/postinstall

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN gauge-ts-template@0.0.1 No repository field.
npm WARN gauge-ts-template@0.0.1 No license field.

added 88 packages from 134 contributors and audited 88 packages in 3.763s

3 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Successfully initialized the project. Run specifications with "gauge run specs/".
  • プロジェクトディレクトリをVSCodeで開いてみると下記のような構造になっているはず
.
├── env
├── logs
├── manifest.json
├── node_modules
├── package-lock.json
├── package.json
├── reports
├── specs
├── tests
└── tsconfig.json
  • 今回用があるのは、spectests のみなので、まずは、spec から見ていく。
  • gaugeの世界には、scenario/ suite/ stepなど色々粒度の異なる概念が登場してくるが、そこら辺の説明は公式に任せる

docs.gauge.org

  • テンプレートで作成した、spec以下には spec > scenario > stepという感じで定義されていて、雑に言えばこの粒度で定義していけば良い。

  • 次にtest側を見ていく。

  • test以下には既に、tsファイルが用意されていて、ファンクションに @step("hogehoge")みたいな物がついている
  • そのアノテーションの中で定義している文字をspecファイル側から呼び出すという簡単な話である

実行する

  • 概要はざっくり説明したので、あとは動くか確認する
$ gauge run
# Specification Heading
  ## Vowel counts in single word     ✔ ✔
  ## Vowel counts in multiple word   ✔ ✔

Successfully generated html-report to => /Users/yuta.aikawa/sources/gauge/sample/reports/html-report/index.html
Specifications: 1 executed  1 passed    0 failed    0 skipped
Scenarios:  2 executed  2 passed    0 failed    0 skipped

Total time taken: 285ms
Updates are available. Run `gauge update -c` for more info.
  • こんな感じで、動けばOK

selenium

  • webdriverを用意する
$ brew tap homebrew/cask
$ brew cask install chromedriver
$ chromedriver -v
$ ChromeDriver 85.0.4183.87 (cd6713ebf92fa1cacc0f1a598df280093af0c5d7-refs/branch-heads/4183@{#1689})
  • 先ほど作成したgaugeプロジェクトで selenium webdriver を使えるようにする
$ npm install -D typescript selenium-webdriver @types/selenium-webdriver
  • 実装していく
  • まずは、specファイル側から定義していく
# Specification Heading

## Sportsnaviにアクセスする
* sportsnaviにアクセスする
  • この状態では対応する実装がないので怒られると思うので、ts側の実装をしていく。

import { Step } from "gauge-ts";
import { Builder, Capabilities, WebDriver } from 'selenium-webdriver'


export default class Access {

  @Step("sportsnaviにアクセスする")
  public async accessSportsNaviTopPage() {
    await access("https://sports.yahoo.co.jp/")
  }
}

# SeleniumWebDriverが用意してるインターフェースを呼び出してDriverの設定を書いておく
const capabilities: Capabilities = Capabilities.chrome()
capabilities.set('chromeOptions', {
  args: [
    '--disable-gpu',
    '--window-size=1024,768'
  ],
  w3c: false
})

# WebDriverを呼び出して受け取ったURLにアクセスする
async function access(url: string): Promise<void> {
  const driver: WebDriver = await new Builder()
    .withCapabilities(capabilities)
    .build()
  try {
    await driver.get(url)
    
  } finally {
    driver && await driver.quit()
  }
}
  • これで実行する
$ gauge run
gauge run
# Specification Heading
  ## Sportsnaviにアクセスする     ✔

Successfully generated html-report to => /Users/yuta.aikawa/sources/gauge/e2e/reports/html-report/index.html
Specifications: 1 executed  1 passed    0 failed    0 skipped
Scenarios:  1 executed  1 passed    0 failed    0 skipped

Total time taken: 2.859s
Updates are available. Run `gauge update -c` for more info.
  • Chromeが立ち上がり、上記な感じで結果が出力される。

ここまでのまとめ

  • setup自体はめちゃくちゃ簡単
  • ただし、seleniumのインターフェースを呼び出して、直接諸々操作しないといけないので、selenideに比べるとキャッチアップにコストがかかりそう。 Selenide: concise UI tests in Java

puppeteerを使ってみる

  • もうちょっと、seleniumとかdriverを意識せずに使いたいので、puppeteerを試してみる github.com
$ npm install -D typescript puppeteer @types/puppeteer
  • 実装側の修正をする
import { Step } from "gauge-ts";
import { launch } from 'puppeteer'


export default class StepImplementation {

  @Step("sportsnaviにアクセスする")
  public async accessSportsNaviTopPage() {
    await access("https://sports.yahoo.co.jp/")
  }
}

async function access(url: string): Promise<void> {
  const browser = await launch(
    { 
        headless: false
    }
  );
  const page = await browser.newPage();
  await page.goto(url)
  await browser.close();
}
  • とりあえず、chroniumが動いてサイトまで飛ぶことはできた。
  • もう一歩踏み込んでselectorを指定して取得する
async function access(url: string): Promise<void> {
  const browser = await launch(
    { 
        headless: false
    }
  );
  const page = await browser.newPage();
  await page.goto(url)
  
  # pageに対してjqueryっぽく指定して要素を取得することができる
  const headersCount = await page.$$eval('#h_nav .clearfix > li', headers => headers.length);
  console.log(headersCount)
  await browser.close();
}
  • もちろんgetElementByIdみたいな感じでJSから取得することも可能
  • ただし、selenideでできていたアサーションに関しては、puppeteerにはないので、jestあたりを使う必要がありそう(expect-pupperteerというのがあるらしい) github.com

まとめ

  • 元々はVSCodeで書ける言語ということで、Java/KotlinからTSへの移行を考えて始めた調査でした。
  • 実際にやってみると、selenideに慣れていたせいもあり、selenideから素のseleniumもしくはpuppeteerへの移行はちょっとハードルが高いなという印象が残った
  • 特に、selenideのアサーション周りで結構楽に実装できていた部分をpuppeteerを使うとしたら頑張る必要がありそう
  • ということで、最終的な結論として、個人的にはselenideに慣れてる人にとってはgaugeの為だけに言語を乗り換えるというのはあまりオススメできない

参考