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をサポートしているので、このように書き直すことができます。
// 昔ながらの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(...)
});
});
});
// 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で書き直すとこのようになります。
expect(...).to.be.true
expect(...).to.have.lengthOf(0)
数が多いと大変ですがchaiのドキュメントを見ながら書き換えていきましょう。自分の場合は正規表現でまとめて置換して対応しました。
ちなみに、chaiはexpect
以外にもassert
やshould
を使うことができます。今回はexpect
を例にしましたが好みに応じて変えてもいいでしょう。
さて、これでテストが動くようになったはずです。試してみましょう!
おや、Error: Failed to load package 'my-atom-package-template'
で動かない?
ようこそ、ここからが実践編です
パッケージがロードできない問題を解決する
さて、エラーの原因を見ると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ではテストが失敗します
なぜか?その理由は、CI上の環境では今テストしようとしているパッケージがAtomにインストールされていないからのようです。
インストールされていないのでconfigDirPath: process.env.ATOM_HOME
の設定をしたところで当然ロードできるわけがない、と。
atom-mocha-test-runnerを使う前は問題なかったのにこれが問題になる理由は不明です。色々調べたものの、残念ながら自分も原因を突き止めることはできませんでした・・・。
この問題のワークアラウンドとして、atom/welcomeが行っているようにatom.packages.activatePackage()
は使わずにテスト対象のパッケージをimportして直接activate()を呼び出して起動することで回避できます。
PackageGeneratorで作られるテストの雛形をこのように変更します。
// 必要な箇所だけ抜粋
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
終わりに
お疲れ様でした。以上で昔の古くてテストの書き方を脱却し、めでたくモダンなテストの書き方ができるようになりました
なかなか長い道のりですがAtom本体のAPIは最近どんどん非同期対応が進んでいるため、テストの方も非同期を扱いやすくしておかないとwaitsForPromiseを至る所で使うハメになりテストが読みづらくなってしまいます。
既にパッケージをメンテしている方も、これから新しいパッケージを作る方もatom-mocha-test-runnerを使ってテストを書いてパッケージをメンテしやすくしていきましょう
番外編 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の設定を変えていたりするようです。
-
一般的にはtest/runner.jsに置くようですが特に制約は存在しないのでお好みのパスに変更可能です。 ↩