RSPEC-设置ENV变量的最佳方法

发布于 2025-02-10 22:57:16 字数 1079 浏览 2 评论 0原文

我在模块上有类方法,然后放置一个常数,并将其称为

module Cryption
 module Crypt
  class << self
    OFF_ENCRYPT = ENV['IS_OFF']
    
    def encrypt
       OFF_ENCRYPT
    end

    def decrypt
       OFF_ENCRYPT
    end
  end
 end
end

模块创建为gem

迫使我在要求之前设置环境变量

ENV["IS_OFF"] = "YES"
require "bundler/setup"
require "cryption"

Cryption::Crypt.encrypt
# result => "YES"

的每个类级别的方法“>

spec_helper.rb

require "bundler/setup"
require "cryption"

crypt_off_spec.rb

RSpec.describe "YES" do
  before(:context) do
    ENV['IS_OFF'] = "YES"
  end
  it "encryption off" do
     expect(Cryption::Crypt.encrypt).to eq("YES")
  end
end

crypt_on_spec.rb

RSpec.describe "NO" do
  before(:context) do
    ENV['IS_OFF'] = "NO"
  end
  it "encryption off" do
     expect(Cryption::Crypt.encrypt).to eq("NO")
  end
end

I have class methods on a module and put a CONSTANT and call it on each class-level method

module Cryption
 module Crypt
  class << self
    OFF_ENCRYPT = ENV['IS_OFF']
    
    def encrypt
       OFF_ENCRYPT
    end

    def decrypt
       OFF_ENCRYPT
    end
  end
 end
end

That module created as a gem

enter image description here

That forces me to set environment variable before require the class

ENV["IS_OFF"] = "YES"
require "bundler/setup"
require "cryption"

Cryption::Crypt.encrypt
# result => "YES"

Is there the proper way set the environment variable on rspec, I think I can't set the variable after require the module

spec_helper.rb

require "bundler/setup"
require "cryption"

crypt_off_spec.rb

RSpec.describe "YES" do
  before(:context) do
    ENV['IS_OFF'] = "YES"
  end
  it "encryption off" do
     expect(Cryption::Crypt.encrypt).to eq("YES")
  end
end

crypt_on_spec.rb

RSpec.describe "NO" do
  before(:context) do
    ENV['IS_OFF'] = "NO"
  end
  it "encryption off" do
     expect(Cryption::Crypt.encrypt).to eq("NO")
  end
end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

泪冰清 2025-02-17 22:57:16

Env Vars并不应该在运行时更改。因此,在测试中调用env ['is_off'] =“ no”并不是一个好主意。问题是,env var是一种全局配置,因此,如果您从一个测试中更改它,则在运行另一个测试时仍将更改该值。

最简单的方法可能是 stub_const 常数的值,例如stub_const“ cryption :: crypt”,“ off_encrypt”,“ false”。这是最好的选择,IMO。

但是,为了完整,我可以向您展示一种实际更改特定测试的环境变量的方法。首先,您需要将常数更改为方法,例如def off_encrypt; env [“ off_encrypt”];结束。要么更改加密/解密仅直接读取env var,例如不是从常数读取。

然后,在您的测试中,您可以做这样的事情:

around(:each) do |example|
  orig_value = ENV["OFF_ENCRYPT"]
  ENV["OFF_ENCRYPT"] = "true" # set the new value
  example.run
ensure
  ENV["OFF_ENCRYPT"] = orig_value
end

再次,我真的不建议这样做,但这是可能的。

Env vars aren't really supposed to be changed at runtime. So, calling ENV['IS_OFF'] = "NO" in your tests isn't really a good idea. The problem is, env vars are a global configuration, so if you change it from one test, the value will still be changed when you run the other test.

The easiest way to do this is probably stub_const, which will temporarily change the value of the constant, e.g. stub_const "Cryption::Crypt", "OFF_ENCRYPT", "false". This is the best option, imo.

But for the sake of completeness, I can show you a way to actually change the environment variable from a specific test. First you would need to change the constant to a method, e.g. def off_encrypt; ENV["OFF_ENCRYPT"]; end. Either that or change the encrypt/decrypt methods to just read the env var directly, e.g. not from a constant.

Then, in your tests, you could do something like this:

around(:each) do |example|
  orig_value = ENV["OFF_ENCRYPT"]
  ENV["OFF_ENCRYPT"] = "true" # set the new value
  example.run
ensure
  ENV["OFF_ENCRYPT"] = orig_value
end

Again, I wouldn't really recommend this, but it is possible.

×纯※雪 2025-02-17 22:57:16

来避免使用反模式

突变env:通常可以轻松避免使用一些最小的TDD重构ENV变量 。 ENV变量通过将ENV变量视为类或实例变量的目的。

从测试的角度来看,如果您想验证方法或类在ENV持有特定值时做正确的事情,则应设计您的应用程序以创建您关心的ENV值的副本,并根据需要突变。另外,如果您要测试是否正确拾取特定值,则可以分叉一个过程,然后更改叉子内的值,这不会影响该父过程。

我建议以下内容:

  1. 重构您的班级将ENV变量用作默认值。
  2. 重构您的类以启用测试或运行时行为以覆盖默认值。
  3. 构建测试可确保ENV [“ Encryption_Enabled”](或您想命名的任何内容)是有效的,并且可以在测试或运行时轻松修改。

示例重构和一些测试骨架,

class Foo

  attr_accessor :encryption_enabled

  # set a default of true (or false if you prefer), or take an
  # optional keyword argument like +encryption_enabled: true+
  # when you initialize an instance of your class
  def initialize
    @encryption_enabled = ENV.fetch "ENCRYPTION_ENABLED", true
  end
end

这将拾取您现有的环境价值,但是您现在拥有一个访问者,可以在其中根据需要更改特定测试。例如:

Rspec.describe Foo
  describe "#encryption_enabled" do
    it "sets an instance variable based on ENV['ENCRYPTION_ENABLED']" do
      pending
    end

    it "defaults to 'true' if ENV['ENCRYPTION_ENABLED'] is unset" do
      pending
    end

    it "can be set to 'false' via its accessor" do
      subject.encryption_enabled = false
      expect(subject).to be_false
    end
  end
end

您还可以更新#New以获取关键字参数以覆盖默认值或执行其他操作。关键是Env应该提供不经常更改的默认值,您的类和方法应在必要时覆盖它们。这为测试提供了更大的灵活性。

您真正需要的唯一与ENV相关的测试是确保读取和解析正确的env值的测试,例如当它们不设置,无效或其他内容时。基于ENV变量的更改运行时行为只是一个反模式,因此这是重构的成熟。

Mutating ENV: Generally an Anti-Pattern Easily Avoided with Some Minimal TDD Refactoring

ENV variables are inherited from Ruby's process environment at startup, and while you can change an ENV variable for the current process and any subprocesses at runtime, you're essentially working against the purpose of ENV variables by treating ENV variables as class or instance variables.

From a testing perspective, if you want to validate that a method or class does the right thing when ENV holds a particular value, you should design your application to create a copy of the ENV values you care about, and mutate those as needed. Alternatively, if you're testing whether specific values are properly picked up, you can fork a process and then change the value within the fork, which will not affect that parent process.

Here's what I'd recommend:

  1. Refactor your class to use the ENV variable as a default.
  2. Refactor your class to enable tests or runtime behavior to override the defaults.
  3. Build tests that ensure ENV["ENCRYPTION_ENABLED"] (or whatever you want to name it) is valid, and can be easily modified within your tests or at runtime.

Example Refactoring and Some Test Skeletons

class Foo

  attr_accessor :encryption_enabled

  # set a default of true (or false if you prefer), or take an
  # optional keyword argument like +encryption_enabled: true+
  # when you initialize an instance of your class
  def initialize
    @encryption_enabled = ENV.fetch "ENCRYPTION_ENABLED", true
  end
end

This will pick up your existing environment value, but you now have an accessor where you can change it as needed for specific tests. For example:

Rspec.describe Foo
  describe "#encryption_enabled" do
    it "sets an instance variable based on ENV['ENCRYPTION_ENABLED']" do
      pending
    end

    it "defaults to 'true' if ENV['ENCRYPTION_ENABLED'] is unset" do
      pending
    end

    it "can be set to 'false' via its accessor" do
      subject.encryption_enabled = false
      expect(subject).to be_false
    end
  end
end

You could also update #new to take a keyword argument to override the default value, or perform other actions. The point is that ENV should provide defaults that don't change often, and your classes and methods should override them when necessary. This provides more flexibility for tests.

The only ENV-related tests you should really need are those that ensure that the ENV values are read and parsed correct, such as when they are unset, invalid, or whatever. Changing runtime behavior based on ENV variables is just an anti-pattern, so this is ripe for refactoring.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文