2

特定の正規表現に一致するファイルの特定の場所から始まるファイルシステムを調べるメソッドを持つモデルがあります。これは after_save コールバックで実行されます。Rspec と FactoryGirl を使用してこれをテストする方法がわかりません。メソッドはテストやコントローラーではなくモデルにあるため、これで FakeFS のようなものを使用する方法がわかりません。FactoryGirl ファクトリで開始する場所を指定したので、セットアップ句でテストによって作成された偽のディレクトリに変更できますか? ディレクトリをモックできますか?これを行うにはおそらくいくつかの異なる方法があると思いますが、どれが最も理にかなっていますか?

ありがとう!

  def ensure_files_up_to_date
    files = find_assembly_files
    add_files = check_add_assembly_files(files)
    errors = add_assembly_files(add_files)
    if errors.size > 0 then
      return errors
    end
    update_files = check_update_assembly_files(files)
    errors = update_assembly_files(update_files)
    if errors.size > 0 then
      return errors
    else
      return []
    end
  end

 def find_assembly_files
    start_dir = self.location
    files = Hash.new
    if ! File.directory? start_dir then
      errors.add(:location, "Directory #{start_dir} does not exist on the system.")
      abort("Directory #{start_dir} does not exist on the system for #{self.inspect}")
    end
    Find.find(start_dir) do |path|
      filename = File.basename(path).split("/").last
      FILE_TYPES.each { |filepart, filehash|
        type = filehash["type"]
        vendor = filehash["vendor"]
        if filename.match(filepart) then
          files[type] = Hash.new
          files[type]["path"] = path
          files[type]["vendor"] = vendor
        end
      }
    end
    return files
  end

  def check_add_assembly_files(files=self.find_assembly_files)
    add = Hash.new
    files.each do |file_type, file_hash|
      # returns an array
      file_path = file_hash["path"]
      file_vendor = file_hash["vendor"]
      filename = File.basename(file_path)
      af = AssemblyFile.where(:name => filename)

      if af.size == 0 then
        add[file_path] = Hash.new
        add[file_path]["type"] = file_type
        add[file_path]["vendor"] = file_vendor
      end
    end
    if add.size == 0 then
      logger.error("check_add_assembly_files did not find any files to add")
      return []
    end
    return add
  end

  def check_update_assembly_files(files=self.find_assembly_files)
    update = Hash.new
    files.each do |file_type, file_hash|
      file_path = file_hash["path"]
      file_vendor = file_hash["vendor"]
      # returns an array
      filename = File.basename(file_path)
      af = AssemblyFile.find_by_name(filename)

      if !af.nil? then
        if af.location != file_path or af.file_type != file_type then
          update[af.id] = Hash.new
          update[af.id]['path'] = file_path
          update[af.id]['type'] = file_type
          update[af.id]['vendor'] = file_vendor
        end
      end
    end
    return update
  end

 def add_assembly_files(files=self.check_add_assembly_files)
    if files.size == 0 then
      logger.error("add_assembly_files didn't get any results from check_add_assembly_files")
      return []
    end
    asm_file_errors = Array.new
    files.each do |file_path, file_hash|
      file_type = file_hash["type"]
      file_vendor = file_hash["vendor"]
      logger.debug "file type is #{file_type} and path is #{file_path}"
      logger.debug FileType.find_by_type_name(file_type)
      file_type_id = FileType.find_by_type_name(file_type).id
      header = file_header(file_path, file_vendor)
      if file_vendor == "TBA" then
        check = check_tba_header(header, file_type, file_path)
        software = header[TBA_SOFTWARE_PROGRAM]
        software_version = header[TBA_SOFTWARE_VERSION]
      elsif file_vendor == "TBB" then
        check = check_tbb_header(header, file_type, file_path)
        if file_type == "TBB-ANNOTATION" then
          software = header[TBB_SOURCE]
        else
          software = "Unified"
        end
        software_version = "UNKNOWN"
      end

      if check == 0 then
        logger.error("skipping file #{file_path} because it contains incorrect values for this filetype")
        asm_file_errors.push("#{file_path} cannot be added to assembly because it contains incorrect values for this filetype")
        next
      end

      if file_vendor == "TBA" then
        xml = header.to_xml(:root => "assembly-file")
      elsif file_vendor == "TBB" then
        xml = header.to_xml
      else
        xml = ''
      end

      filename = File.basename(file_path)
      if filename.match(/~$/) then
        logger.error("Skipping a file with a tilda when adding assembly files.  filename #{filename}")
        next
      end
      assembly_file = AssemblyFile.new(
                    :assembly_id => self.id,
                    :file_type_id => file_type_id,
                    :name => filename,
                    :location => file_path,
                    :file_date => creation_time(file_path),
                    :software => software,
                    :software_version => software_version,
                    :current => 1,
                    :metadata => xml
                )

      assembly_file.save! # exclamation point forces it to raise an error if the save fails
    end # end files.each

    return asm_file_errors
  end
4

1 に答える 1

1

簡単な答え: 他のメソッドと同様に、モデル メソッドをスタブ化できます。モデルの特定のインスタンスをスタブしてから、スタブfindまたはそれを返すものをスタブするか、any_instanceどのモデルが関係しているか心配したくない場合はスタブアウトします。何かのようなもの:

it "does something" do
  foo = Foo.create! some_attributes
  foo.should_receive(:some_method).and_return(whatever)
  Foo.stub(:find).and_return(foo)
end

本当の答えは、コードが複雑すぎて効果的にテストできないということです。モデルは、ファイルシステムが存在することさえ認識してはなりません。その動作は、個別にテストできる他のクラスにカプセル化する必要があります。その後、モデルafter_saveはそのクラスの単一のメソッドを呼び出すことができ、その単一のメソッドが呼び出されるかどうかのテストがはるかに簡単になります。

あなたのメソッドは、あまりにも多くのことをしようとしているため、テストするのも非常に困難です. 条件付きロジックと外部依存関係はすべて、テストしたいさまざまな部分に到達するために、大量のモックを作成する必要があることを意味します。

これは大きなトピックであり、良い答えはこの答えの範囲をはるかに超えています。SOLID に関するウィキペディアの記事から始めて、懸念を個々のクラスに分離し、小さな構成されたメソッドを使用することの背後にあるいくつかの理由について、そこから読んでください。大まかに言えば、複数の分岐または 10 行を超えるコードを持つメソッドは大きすぎます。コードが約 100 行を超えるクラスは大きすぎます。

于 2013-04-19T21:51:27.937 に答える