FakeFS でファイルアクセスをテストする
ファイルシステムの操作に関連するクラスのモックオブジェクトを提供するライブラリ「FakeFS」を使ってみたので、使い方をメモしておきます。
defunkt/fakefs
https://github.com/defunkt/fakefs
インストール
% gem install fakefs --no-rdoc --no-ri
Rails を使う場合は Gemfile に以下のように追加します。
group :test do gem 'fakefs', :require => "fakefs/safe" end
使ってみる
FakeFS を require すると以下のクラスが FakeFS が提供するクラスに差替えられます。
- File
- FileTest
- FileUtils
- Dir
- Pathname
そのため、これらのクラスを使用してファイルシステムにアクセスするコードを、実際のファイルシステムに変更を加える事なくテストする事ができます。
まずは「fakefs」を require して使ってみます。
# -*- coding: utf-8 -*- require 'fakefs' TEST_DIR = '/temp/example' # ディレクトリの作成 # 全てのパスがモック化されているため、中間パスが実在する場合でも「Errno::ENOENT」になる # そのためフルパスで指定する場合は mkdir_p などの中間パスも作成してくれるメソッドを使う p FileUtils.mkdir_p TEST_DIR #=> ["/temp/example"] # ディレクトリの削除 # この行が無くても実際のパスにディレクトリは作成されない # また、削除自体もエラーにはならず成功する p Dir.rmdir TEST_DIR #=> true
「require 'fakefs'」した場合、FakeFS はその時点でファイルシステム関連のクラスを差替えてしまいます。
使用しているコード内(ライブラリ内も含む)でのあらゆるファイルアクセスをモック化して問題ない場合以外では、この挙動は少々微妙かも知れません。
そういった場合には「fakefs/safe」の方を require します。
「fakefs/safe」を require した場合のサンプルコードは以下のようになります。
# -*- coding: utf-8 -*- require 'fakefs/safe' TEST_DIR = '/temp/' TEST_FILE = '/temp/example.txt' # FakeFS 有効 FakeFS.activate! # ファイルシステムを操作する処理 Dir::mkdir TEST_DIR File.open(TEST_FILE, 'w') { |f| f.write 'Hello, World.' } # FakeFS 無効 FakeFS.deactivate!
上記のように、明示的に FakeFS.activate! を呼ばない限り FakeFS が有効になりません。
また、 FakeFS.deactivate! を呼ぶ事で通常のファイルアクセスが可能になるよう、挙動を戻す事も出来ます。
他にも、以下のようにブロックを渡すと、ブロック内でのみ FakeFS が有効になります。
# -*- coding: utf-8 -*- require 'fakefs/safe' TEST_DIR = '/temp/' TEST_FILE = '/temp/example.txt' FakeFS do Dir::mkdir TEST_DIR File.open(TEST_FILE, 'w') { |f| f.write 'Hello, World.' } end
こんな感じでとても簡単に使用する事ができるので、既存のテストコードに導入するのも容易だと思います。
RSpec で使う
FakeFS では RSpec と一緒に使うための spec_helpers.rb というファイルも提供されています。
簡単に試してみるため、まずは RSpec を使ったサンプルプロジェクトを用意してみました。
ファイル配置は以下のような感じ。
exampleapp ├── Rakefile ├── example.rb └── spec ├── example_spec.rb └── spec_helper.rb
Rakefile と spec_helper.rb は以下のような内容で用意しました。
# -*- coding: utf-8 -*- require "rspec/core/rake_task" desc 'rake spec' task :default => [:spec] RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/*_spec.rb' spec.rspec_opts = ['-cfd --backtrace'] end
- spec_helper.rb
# -*- coding: utf-8 -*- $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' RSpec.configure do |config| end
次にテスト対象クラスです。
若干無理矢理感溢れてますが、適当なものを思いつかなかった為、テスト対象とする Example クラスは以下のような内容とします。
- example.rb
# -*- coding: utf-8 -*- class Example LOG_FILE = 'example.log' def self.create_dir(dir) FileUtils::mkdir_p dir end def self.log(message) File.open(LOG_FILE, 'w') { |f| f.write message } end end
spec はこんな感じで適当に。
- example_spec.rb
# -*- coding: utf-8 -*- require_relative 'spec_helper' require_relative '../example' describe Example do describe ".create_dir" do subject { Example } before { subject.create_dir("testdir") } it "ディレクトリが作成されること" do expect(File.exists? "testdir").to be_true end end describe ".log" do subject { Example } before { subject.log("Hello, World.") } it "ログファイルが作成されること" do expect(File.exists? Example::LOG_FILE).to be_true end end end
ここで試しに rake spec してみると、カレントディレクトリ内に「testdir」ディレクトリと「example.log」というファイルが作成されてしまいます。
テストの度にこういったファイルやディレクトリが作成されてしまうのは煩わしいですよね。
という訳で、毎回後始末をするのも面倒なので FakeFS の出番になります。
example_spec.rb に以下のように二行追加します。
- example_spec.rb
# -*- coding: utf-8 -*- require_relative 'spec_helper' require 'fakefs/spec_helpers' # <= 追加 require_relative '../example' describe Example do include FakeFS::SpecHelpers # <= 追加 describe ".create_dir" do subject { Example } before { subject.create_dir("testdir") } it "ディレクトリが作成されること" do expect(File.exists? "testdir").to be_true end end describe ".log" do subject { Example } before { subject.log("Hello, World.") } it "ログファイルが作成されること" do expect(File.exists? Example::LOG_FILE).to be_true end end end
これでこの spec 内で FakeFS が有効になります。
今度はテストを実行してもディレクトリやファイルが作成されません。
ただ、もし複数の spec ファイルでファイルシステムを扱っているような場合、全部のファイルで上の二行を追加するのは面倒です。
そういった場合は、以下のように spec_helper.rb に記述する事で、全ての spec ファイルで FakeFS を有効にすることができます。
- spec_helper.rb
# -*- coding: utf-8 -*- $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' require 'fakefs/spec_helpers' # <= 追加 RSpec.configure do |config| config.include FakeFS::SpecHelpers # <= 追加 end
ファイルシステムを操作するようなクラスのテストは結構面倒だと思っていたのですが、FakeFS を使うと余り色々な事を気にせずにテストが書けるのでいい感じですね。
参考
Ruby - FakefsでTest-Drivenな運用スクリプト - Qiita [キータ]
http://qiita.com/sawanoboly/items/de2b14779796179ef0d1