2
3

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.

AtomAdvent Calendar 2017

Day 10

atom-mocha-test-runnnerの使い方(実践編)

Last updated at Posted at 2017-12-09

Atomアドベントカレンダー2017の10日目の記事です。

今回は前回紹介したatom-mocha-test-runnerの詳しい使い方を紹介します。

atom-mocha-test-runnerをテストランナーに設定する

まずは前回紹介したようにatom-mocha-test-runnerでテストが走るようにセットアップしましょう。

  • atom-mocha-test-runner, chaiをインストール
  • pakcage.jsonに"atomTestRunner": "atom-mocha-test-runner"を追加
  • spec/にあるspec.jsを.test.jsにリネーム

ここまでできたらapm testを実行しましょう。

Error: Invalid Chai property: toBe. Did you mean "to"?

といったAtomが元々用意してくれていたアサーションを使えないエラーが出ればOKです。
実行しても何も表示されなかったり、テストがpassしてしまった場合は正しく設定できていないので再度確認してみてください

specをmochaとchaiで動くように書き直す

テストがmochaで走るようになったことで元々Atomが用意していたwaitsForPromise, runsといった非同期テストのための構文や、toBe, toHaveLengthといったアサーションが使えなくなっています。
これらを書き換えてテストが動く動くようにしていきましょう。ちなみにここではアサーションライブラリとしてchaiを使いますが、atom-mocha-test-runnerはchaiに依存しているわけではないので好みのアサーションライブラリに差し替えが可能です。

非同期処理のテスト

まずは非同期処理のためにかつて使われていたwaitsForPromise, runsとおさらばしましょう。

今のAtomとmochaはasync/awaitをサポートしているので、このように書き直すことができます。

my-atom-package-template-spec.js
// 昔ながらのwaitsForPromise, runcを使う場合
// 説明に必要なところだけ抜粋
beforeEach(() => {
  activationPromise = atom.packages.activatePackage('my-atom-package-template');
});

describe('...', () => {
  it('...', () => {
    atom.commands.dispatch(workspaceElement, 'my-atom-package-template:toggle');

	// Promiseがresolveされるまで待つ
    waitsForPromise(() => {
      return activationPromise;
    });

    // waitsForPromiseが完了したら実行される
    runs(() => {
      expect(...)
    });
  });
});
my-atom-package-template.test.js
// async/awaitで書き直した場合
// 説明に必要なところだけ抜粋
beforeEach(() => {
  activationPromise = atom.packages.activatePackage('my-atom-package-template');
});

describe('...', () => {
  // itにasyncを付ける
  it('...', async () => {
    atom.commands.dispatch(workspaceElement, 'my-atom-package-template:toggle');

	// Promiseがresolveされるまで待つ
	await activationPromise

	expect(...)
  });
});

今時のjsのテストフレームワークと同じようにasync/awaitが使えることでだいぶスッキリしましたね!

ちなみにmochaはasync/awaitを使う方法以外の非同期処理を扱う構文も存在しているので、一度mochaのドキュメントを見てみると良いでしょう。

アサーションの書き換え

次はexpect, toBeといったアサーションの書き換えです。

expectについてはchaiをimportするだけで書き換える必要はないです。

問題はtoBe(true)やtoHaveLength(0)などのアサーションで、chaiでも同等の機能が存在するのですが名前が変わっています。
例えば上の2つはchaiで書き直すとこのようになります。

chai.test.js
expect(...).to.be.true
expect(...).to.have.lengthOf(0)

数が多いと大変ですがchaiのドキュメントを見ながら書き換えていきましょう。自分の場合は正規表現でまとめて置換して対応しました。

ちなみに、chaiはexpect以外にもassertshouldを使うことができます。今回はexpectを例にしましたが好みに応じて変えてもいいでしょう。

さて、これでテストが動くようになったはずです。試してみましょう!
おや、Error: Failed to load package 'my-atom-package-template'で動かない?
ようこそ、ここからが実践編です :innocent:

パッケージがロードできない問題を解決する

さて、エラーの原因を見るとatom.pakcages.activatePackage('...')したときにそのパッケージを読み込めないことが原因らしいです。
この理由は、atom-mocha-test-runnerがデフォルトの設定だとglobal.atomにセットするatomのオブジェクトを作るときconfigDirPathにテスト用の空のディレクトリをセットしているからのようです。

どうやらbuildAtomEnvironment()というのを使えばatomのオブジェクトを作れることがコードから推測できます。
試しにgithubでatomのソースコードからbuildAtomEnvironmentを検索するとjasmine-test-runner.coffeeというファイルが見つかりました。おそらくこれがAtomに元々組み込まれているテストランナーでしょう。
その中をさらに検索してみるとwindow.atomにセットしているそれらしい処理があります。

configDirPathにprocess.env.ATOM_HOMEというのを渡しているので、昔のAtomのテストランナーではおそらく~/.atomのパスを渡すことで自分のAtomの環境を使ってテストを走らせていたのだろうということが推測できます。

原因と対策がわかったので先程のテストの中に組み込みましょう。
beforeEachの中でこのようにwindow.atomに代入します。

window.atom = global.buildAtomEnvironment({
  window: window,
  configDirPath: process.env.ATOM_HOME,
  enablePersistence: false
})

これでめでたくテストが通ったはずです!

CIに乗せる

めでたくテストが通ったのでatom/ciから.travis.ymlあたりをコピーしてCIサービスでテストが走るようにしてみましょう。

はい、残念ながらCIではテストが失敗します :innocent:

なぜか?その理由は、CI上の環境では今テストしようとしているパッケージがAtomにインストールされていないからのようです。
インストールされていないのでconfigDirPath: process.env.ATOM_HOMEの設定をしたところで当然ロードできるわけがない、と。
atom-mocha-test-runnerを使う前は問題なかったのにこれが問題になる理由は不明です。色々調べたものの、残念ながら自分も原因を突き止めることはできませんでした・・・。

この問題のワークアラウンドとして、atom/welcomeが行っているようにatom.packages.activatePackage()は使わずにテスト対象のパッケージをimportして直接activate()を呼び出して起動することで回避できます。

PackageGeneratorで作られるテストの雛形をこのように変更します。

my-atom-package-template.test.js
// 必要な箇所だけ抜粋
import myAtomPackageTemplate from '../lib/my-atom-package-template';

describe('', () => {
  it('', async () => {
    // activate()をawaitで待たないと次のdispatch()が失敗することに注意
    await myAtomPackageTemplate.activate({});
    atom.commands.dispatch(workspaceElement, 'my-atom-package-template:toggle');
  });
)};

PackageGeneratorによって生成されるパッケージの雛形に全てここまで紹介した内容を全て適用したコードはこちらになります。
https://github.com/Kesin11/MyAtomPackageTemplate/tree/v1

終わりに

お疲れ様でした。以上で昔の古くてテストの書き方を脱却し、めでたくモダンなテストの書き方ができるようになりました :tada:

なかなか長い道のりですがAtom本体のAPIは最近どんどん非同期対応が進んでいるため、テストの方も非同期を扱いやすくしておかないとwaitsForPromiseを至る所で使うハメになりテストが読みづらくなってしまいます。

既にパッケージをメンテしている方も、これから新しいパッケージを作る方もatom-mocha-test-runnerを使ってテストを書いてパッケージをメンテしやすくしていきましょう :smile:

番外編 runner.jsを用意する

ここまででatom-mocha-test-runnerを使ったテストの実行は完了ですが、テスト周りの細かい設定を変更したい場合はpackage.jsonのatomTestRunner: "atom-mocha-test-runner"atomTestRunner: "spec/runner.js"に変更し1、runner.jsでcreateRunner()にオプションを渡すことでデフォルトの挙動から変更できます。

例えばtestSuffixes: ['-spec.js', '-spec.coffee']というオプションを設定すれば従来通り**-spec.js, **-spec.coffeeというファイル名がテストファイルとして見なされるように変更可能です。
どのようなオプションが設定できるかはatom-mocha-test-runnerのREADMEに記載されています。

他にも例えばatom/githubを見るとglobal.assert = chai.assertとして各テストの中でimportを省略できるようにしたり、mochaの設定を変えていたりするようです。

  1. 一般的にはtest/runner.jsに置くようですが特に制約は存在しないのでお好みのパスに変更可能です。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?