RSpec在未知(看似具体)的情况下失败

时间:2016-11-16 20:52:28

标签: ruby rspec

我对ruby比较新,从Python和C#这样的语言进入它,所以请原谅我,如果这是一个我错过的明显错误。

问题

我正在编写一个工具代码来帮助自己和其他开发人员生成.editorconfig文件,以此来学习Ruby。我使用rspec作为我的测试框架并编写了以下测试:

module EditorConfigGenerator

  RSpec.describe EditorConfigGenerator::FileGenerator do
    configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
      end_of_line: 'lf', charset: 'utf-8',
    trim_trailing_whitespace: true, insert_final_newline: true})]
    file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)

    context "A collection of one EditorConfig object is provided" do
      it "displays a preview of the file output" do
        # Newlines are automatically inserted into ruby multi-line strings
        output = "root = true
                [*]
                indent_style = space
                indent_size = 2
                end_of_line = lf
                charset = utf-8
                trim_trailing_whitespace = true
                insert_final_newline = true
      "
        # String.delete(' ')  to remove any potential formatting discrepancies
        # which don't affect the test of the content.
        expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
      end

      it "creates a .editorconfig file in the correct location" do
        dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
        expect(dot_editorconfig.class).to eq File
        expect(File.exist? "test_output/.editorconfig").to eq true
      end
    end
    context "A collection of multiple EditorConfig objects is provided" do
      it "displays a preview of the file output" do
        configs = configs_sensible_defaults.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
        file_generator = EditorConfigGenerator::FileGenerator.new(configs)
        output = "root = true
                [*]
                indent_style = space
                indent_size = 2
                end_of_line = lf
                charset = utf-8
                trim_trailing_whitespace = true
                insert_final_newline = true
                [*.{js,py}]
                indent_size = 4"

        expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
      end
    end
  end
end

当我最初几次进行这些测试时,他们看起来很好。然后,我在spec_helper.rb文件

中启用了以下建议行
  config.profile_examples = 10
  config.order = :random
  kernel.srand config.seed

我再次运行了几次测试,所有测试都经过 - 看似随机 - 我用种子31166获得了以下失败输出:

  1) EditorConfigGenerator::FileGenerator A collection of one EditorConfig object is provided displays a preview of the file output
     Failure/Error: expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')

       expected: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n"
            got: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n[*.{js,py}]\nindent_size=4"

       (compared using ==)

       Diff:
       @@ -6,4 +6,6 @@
        charset=utf-8
        trim_trailing_whitespace=true
        insert_final_newline=true
       +[*.{js,py}]
       +indent_size=4

     # ./spec/file_generator_spec.rb:23:in `block (3 levels) in <module:EditorConfigGenerator>'

我尝试了其他种子,但它有效,但种子31166失败了。

修复

输出让我觉得它是上下文的,所以我试图在我的实现中寻找一个bug。我没有找到一个,所以我认为这可能是我在规范中定义共享变量configs_sensible_defaults的方式的问题。

我决定更改代码以使用before(:each)在每次测试之前分配一个实例变量(即正确地重置数据),它似乎解决了它。

这里是固定的spec/file_generator_spec.rb

module EditorConfigGenerator
  RSpec.describe EditorConfigGenerator::FileGenerator do
    before(:each) do
      @configs_sensible_defaults =  [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
      @file_generator = EditorConfigGenerator::FileGenerator.new(@configs_sensible_defaults)
    end

    context "A collection of one EditorConfig object is provided" do
      it "displays a preview of the file output" do
        # Newlines are automatically inserted into ruby multi-line strings
        output = "root = true
                [*]
                indent_style = space
                indent_size = 2
                end_of_line = lf
                charset = utf-8
                trim_trailing_whitespace = true
                insert_final_newline = true
      "
        # String.delete(' ')  to remove any potential formatting discrepancies
        # which don't affect the test of the content.
        expect(@file_generator.preview_output.delete(' ')).to eq output.delete(' ')
      end

      it "creates a .editorconfig file in the correct location" do
        dot_editorconfig = @file_generator.generate_config_file('test_output/.editorconfig')
        expect(dot_editorconfig.class).to eq File
        expect(File.exist? "test_output/.editorconfig").to eq true
      end
    end
    context "A collection of multiple EditorConfig objects is provided" do
      it "displays a preview of the file output" do
        configs = @configs_sensible_defaults.clone.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
        @file_generator = EditorConfigGenerator::FileGenerator.new(configs)
        output = "root = true
                [*]
                indent_style = space
                indent_size = 2
                end_of_line = lf
                charset = utf-8
                trim_trailing_whitespace = true
                insert_final_newline = true
                [*.{js,py}]
                indent_size = 4"

        expect(@file_generator.preview_output.delete(' ')).to eq output.delete(' ')
      end
    end
  end
end

这是两个文件(view on Github)的差异输出:

 RSpec.describe EditorConfigGenerator::FileGenerator do
    configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
      end_of_line: 'lf', charset: 'utf-8',
    trim_trailing_whitespace: true, insert_final_newline: true})]
    file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)
    before(:each) do
      @configs_sensible_defaults =  [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
      @file_generator = EditorConfigGenerator::FileGenerator.new(@configs_sensible_defaults)
    end

    context "A collection of one EditorConfig object is provided" do
      it "displays a preview of the file output" do
@ file_generator_spec.rb:22 @ module EditorConfigGenerator
      "
        # String.delete(' ')  to remove any potential formatting discrepancies
        # which don't affect the test of the content.
        expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
        expect(@file_generator.preview_output.delete(' ')).to eq output.delete(' ')
      end

      it "creates a .editorconfig file in the correct location" do
        dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
        dot_editorconfig = @file_generator.generate_config_file('test_output/.editorconfig')
        expect(dot_editorconfig.class).to eq File
        expect(File.exist? "test_output/.editorconfig").to eq true
      end
    end
    context "A collection of multiple EditorConfig objects is provided" do

最后,对于上下文,以下是相关的file_generator.rbeditor_config.rb文件

module EditorConfigGenerator
  class FileGenerator
    def initialize(configs)
      @configs = configs
    end

    def preview_output
      output = ""
      @configs.each do |config|
        if output.include? "root="
          output << config.to_s_without_root
          next
        end
        output << config.to_s
      end
      return output.rstrip if @configs.size > 1
      output
    end

    def generate_config_file(location='.editorconfig')
      File.delete(location) if File.exist? location
      file = File.new(location, "w")
      file.print(preview_output)
      file.close
      file
    end
  end
end

module EditorConfigGenerator
  class EditorConfig
    attr_reader :root, :indent_style, :indent_size,
    :end_of_line, :charset, :trim_trailing_whitespace,
    :insert_final_newline, :file_type

    def initialize(options)
      @root = nil
      @file_type = :*

      transform_options(options)
      set_options(options)
    end

    def set_options(options)
      @root = options[:root] unless options[:root].nil?
      @indent_style = options[:indent_style] unless options[:indent_style].nil?
      @indent_size = options[:indent_size] unless options[:indent_size].nil?
      @end_of_line = options[:end_of_line] unless options[:end_of_line].nil?
      @charset = options[:charset] unless options[:charset].nil?
      @trim_trailing_whitespace = options[:trim_trailing_whitespace] unless options[:trim_trailing_whitespace].nil?
      @insert_final_newline = options[:insert_final_newline] unless options[:insert_final_newline].nil?
      @file_type = options[:file_type] unless options[:file_type].nil?
    end

    def transform_options(options)
      options[:root] = true if options[:root] == 'y'
      options[:root] = false if options[:root] == 'n'
      options[:trim_trailing_whitespace] = true if options[:trim_trailing_whitespace] == 'y'
      options[:trim_trailing_whitespace] = false if options[:trim_trailing_whitespace] == 'n'
      options[:insert_final_newline] = true if options[:insert_final_newline] == 'y'
      options[:insert_final_newline] = false if options[:insert_final_newline] == 'n'
    end

    def to_s
      config_string = ""
      config_string << "root = #{@root.to_s}\n" unless @root.nil?
      config_string << "[#{@file_type}]\n"
      config_string << "indent_style = #{@indent_style}\n" unless @indent_style.nil?
      config_string << "indent_size = #{@indent_size}\n" unless @indent_size.nil?
      config_string << "end_of_line = #{@end_of_line}\n" unless @end_of_line.nil?
      config_string << "charset = #{@charset}\n" unless @charset.nil?
      config_string << "trim_trailing_whitespace = #{@trim_trailing_whitespace.to_s}\n" unless @trim_trailing_whitespace.nil?
      config_string << "insert_final_newline = #{@insert_final_newline.to_s}\n" unless @insert_final_newline.nil?
      config_string
    end

    def to_s_without_root
      lines = to_s.lines
      lines.delete_at 0
      lines.join
    end

    def to_s_without_line(line_identifier)
      lines = to_s.lines
      index = lines.index(line_identifier)
      lines.delete_at index
      lines.join
    end
  end
end

问题

有人可以解释为什么更改解决了这个问题? 更改是否解决了问题?

我认为,configs_sensible_defaults中的集合被突变并且在每次运行后没有被正确重置,这可以解释为什么某个种子会触发失败(可能在种子测试下) 2将在1)之前运行。

1 个答案:

答案 0 :(得分:2)

它有时而不是其他时间工作的原因是因为你的一个测试(在第二个上下文中)被重新分配file_generator。第一个上下文中的测试是使用共享外部作用域中的file_generator。只要该测试首先运行,它就按预期工作。然后,第二个范围中的第二个测试运行,重新分配file_generator,并且也通过了。

由于测试以随机顺序运行,每当它们按照文件中的顺序运行时,一切都在通过。当第二个测试首先运行时,它覆盖file_generator,第二个测试仍然通过,但第一个测试以覆盖的值运行。

您可以使用before块为每个测试配置内容,但您应该只在前一个块中配置公共,共享值。对于每个上下文或每个测试,任何不同的东西都应该在接近使用它的范围内进行初始化。

在上面的示例中,我不会使用任何before块,至少不在外部范围内。 @config_sensible_defaults@file_generator在每个上下文中都不同,根本不应该共享。如果要在具有相同默认值和生成器的上下文中将一组测试组合在一起,可以将before放在 context块中,以正确初始化事物对于每个背景。

相关问题