У меня есть модель, в которой есть метод, который просматривает файловую систему, начиная с определенного места, на предмет файлов, соответствующих определенному регулярному выражению. Это выполняется в обратном вызове 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
2
Denise Mauldin 19 Апр 2013 в 21:52

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 строк кода, слишком велик.

1
Jim Stewart 20 Апр 2013 в 01:51