17
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptAdvent Calendar 2019

Day 3

jscodeshiftのテストを書く

Last updated at Posted at 2019-12-03

(用意していた記事が壮大になってきて、本日中に書き終わらないと思ったので、急遽極小ネタに切り替えました。)

前日、同僚の@toshi__tomaがjscodeshiftについて投稿しているのですが、TDD的にAST変換を書けるのがjscodeshiftの強みの一つかなと思ったので、jscodeshiftのテストについて書きます。@toshi__tomaの記事↓と合わせて読んでいただけると、ちょうどよいかな〜と思います。

JavaScriptのリファクタリングツール「jscodeshift」の使い方 - Qiita

準備

jscodeshiftとjestをインストールします。

$ npm i -D jscodeshift jest

何も変換しないtransformer.jsを用意します。

// transformer.js
// 変換せず、そのままソースコードを返すだけ
module.exports = function (fileInfo) {
  return fileInfo.source;
}

テストを書く

「書く」と言ってもとても簡単で、変換前のコードと変換後に期待されるコードを用意し、テストファイルでそれらを読み込む関数を実行するだけです。

テストデータの用意

まず、変換前後のコードを置く__testfixtures__ディレクトリを作成します。

$ mkdir __testfixtures__

変換前後のコードを適当に作ります。変換前のコードは.input.js、変換後に期待されるコードは.output.jsの拡張子を付けます。「何も変換しない」ので、まずは全く同じコードを置いてみましょう。

$ echo "console.log('hoge');" > __testfixtures__/hoge.input.js
$ cp __testfixtures__/hoge.input.js __testfixtures__/hoge.output.js

テストファイルの設置

テストファイルを__tests__/以下に置きます。jestは、__tests__以下にテストファイルを置く方法以外に、テストファイルの拡張子を.test.js.spec.jsにする方法もありますが、変換処理が記述されたJSファイル(今回の場合、transformer.js)や__testfixtures__が、テストファイルの一つ上の階層にあることを前提に処理するので、テストファイルは__tests__以下に置きましょう。

$ mkdir __tests__
$ vim __tests__/transformer.js

テストケースの定義

jscodeshift/dist/testUtilsに定義されているdefineTest関数を使うと、テストケースを定義できます。defineTest関数の内部でjestのdescribe関数やit関数を使ってテストケースを追加してくれるので、テストファイルにはdefineTest関数以外に記述する必要はありません。

defineTest関数の引数は以下のとおりです。

  • 第1引数: テストファイルのディレクトリパスなので常に__dirnameを渡す
  • 第2引数: テスト対象の変換処理が記述されたJSファイル名(拡張子不要)
  • 第3引数: 変換処理JSに渡すオプション。無ければnull
  • 第4引数: テストデータのプリフィックス。__testfixtures__/第4引数.(input|output).jsがテストデータに利用される
// __tests__/transformer.js
const { defineTest } = require('jscodeshift/dist/testUtils');

// transformer.jsに対して、
// __testfixtures__/hoge.(input|output).jsを使ってテストする
defineTest(__dirname, 'transformer', null, 'hoge');

ちなみに、defineTest関数の第4引数を省略するとテストデータのプリフィックスとして第2引数が利用されます。

// 第4引数の省略
// transformer.jsに対して、
// __testfixtures__/transformer.(input|output).jsを使ってテストする
defineTest(__dirname, 'transformer');

では、テストを実行してみましょう。何も変換していないので、PASSします。

$ npx jest
 PASS  __tests__/transformer.js
  transformer
    ✓ transforms correctly using "hoge" data (157ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.814s, estimated 1s
Ran all test suites.

console.log('hoge');console.log('fuga');に変換するように修正してみましょう。__testfixtures__/hoge.output.jsconsole.log('fuga');に書き換え、テストを再実行します。ちゃんと、FAILしてますね。

# 期待する結果を変更
$ echo "console.log('fuga');" > __testfixtures__/hoge.output.js

# テストを再実行
$ npx jest
 FAIL  __tests__/transformer.js
  transformer
    ✕ transforms correctly using "hoge" data (54ms)

  ● transformer › transforms correctly using "hoge" data

    expect(received).toEqual(expected) // deep equality

    Expected: "console.log('fuga');"
    Received: "console.log('hoge');"

      at runInlineTest (node_modules/jscodeshift/dist/testUtils.js:36:33)
      at runTest (node_modules/jscodeshift/dist/testUtils.js:73:3)
      at Object.<anonymous> (node_modules/jscodeshift/dist/testUtils.js:90:7)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.712s, estimated 1s
Ran all test suites.

テストが通るようにtransformer.jsを修正します。

// transformer.js
module.exports = (fileInfo, { jscodeshift: j }) => {
  const root = j(fileInfo.source);

  root.find(j.Literal, { value: "hoge" }).forEach(path => {
    path.replace(j.literal("fuga"));
  });

  return root.toSource({ quote: "single" });
};

テストを再実行すると、PASSします!

$ npx jest
 PASS  __tests__/transformer.js
  transformer
    ✓ transforms correctly using "hoge" data (113ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.276s
Ran all test suites.

このように、.output.jsを修正→テストが通るまで実装を繰り返すことで、進捗を確認しながら着実に進めることができます。

defineTest関数以外にも、jscodeshift/dist/testUtilsには以下のような関数が定義されています。お好みで使い分けましょう。

  • defineInlineTest関数
    • テストデータを直接文字列で渡す版
  • defineSnapshotTest関数
    • jestのtoMatchSnapshotを使って、回帰テストを書くことができる
  • applyTransform関数
    • 変換を実行する関数。入力を引数に渡し、出力を返り値で受け取る

おわり

jscodeshiftのテストの書き方を紹介しました。jscodeshiftは、ASTの入門としておすすめですので、興味のある方はぜひ触ってみましょう〜

17
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?