You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用rspec/mocha测试Facter fact时出现错误求助

解决Facter Fact的RSpec/Mocha测试问题

先把你的Fact代码补全(看起来最后一行被截断了),完整版本大概是这样:

# Kubadmin facts
require 'json'

Facter.add(:has_kubectl) do
  confine :kernel => :linux
  setcode do
    Facter::Core::Execution.which('kubectl')
  end
end

Facter.add(:kubernetes_version) do
  confine :kernel => :linux
  setcode do
    kubernetes_json = Facter::Core::Execution.execute('/usr/bin/kubectl version -o json')
    kubernetes_parsed_json = JSON.parse(kubernetes_json)
    versions = {
      "client" => kubernetes_parsed_json['clientVersion']['gitVersion'],
      "server" => kubernetes_parsed_json['serverVersion']['gitVersion']
    }
    versions.to_json
  end
end

首先排查Fact代码本身的问题

你原来的代码有个关键错误:kubernetes_json = ...这段代码写在了setcode块外面,这会导致Facter加载这个文件时就直接执行kubectl命令,而不是在fact被求值的时候执行,而且会忽略confine :kernel => :linux的限制,这很可能是测试报错的根源之一。必须把所有需要执行的逻辑放进setcode块里。

另外,还要处理两种异常场景:

  • 系统中没有kubectl,执行命令会失败
  • kubectl version返回的JSON格式不符合预期,或者连接不到K8s集群导致返回错误JSON

修正后的Fact代码可以加上异常处理:

# Kubadmin facts
require 'json'

Facter.add(:has_kubectl) do
  confine :kernel => :linux
  setcode do
    !!Facter::Core::Execution.which('kubectl') # 返回布尔值更符合fact的语义
  end
end

Facter.add(:kubernetes_version) do
  confine :kernel => :linux
  confine :has_kubectl => true # 依赖has_kubectl的结果,避免无kubectl时执行命令

  setcode do
    begin
      kubernetes_json = Facter::Core::Execution.execute(
        '/usr/bin/kubectl version -o json',
        :on_fail => nil # 命令失败时返回nil,而不是抛出异常
      )
      if kubernetes_json
        kubernetes_parsed_json = JSON.parse(kubernetes_json)
        {
          "client" => kubernetes_parsed_json.dig('clientVersion', 'gitVersion'),
          "server" => kubernetes_parsed_json.dig('serverVersion', 'gitVersion')
        }.compact # 过滤掉nil值
      else
        nil
      end
    rescue JSON::ParserError => e
      Facter.debug("Failed to parse kubectl version JSON: #{e.message}")
      nil
    end
  end
end

然后编写RSpec/Mocha测试用例

测试的核心是mock Facter::Core::Execution的方法,避免真的调用系统命令,同时模拟不同的场景:

require 'spec_helper'
require 'facter'
require 'json'

describe 'kubadmin facts' do
  before do
    # 清除已加载的facts,避免测试污染
    Facter.clear
    Facter.clear_messages
    # 加载我们的fact文件(替换成你的实际路径)
    load '/path/to/your/kubadmin_facts.rb'
  end

  describe ':has_kubectl' do
    context 'on Linux system' do
      before { Facter.fact(:kernel).stubs(:value).returns('Linux') }

      it 'returns true when kubectl exists' do
        Facter::Core::Execution.expects(:which).with('kubectl').returns('/usr/bin/kubectl')
        expect(Facter.value(:has_kubectl)).to be true
      end

      it 'returns false when kubectl does not exist' do
        Facter::Core::Execution.expects(:which).with('kubectl').returns(nil)
        expect(Facter.value(:has_kubectl)).to be false
      end
    end

    context 'on non-Linux system' do
      before { Facter.fact(:kernel).stubs(:value).returns('Darwin') }

      it 'is not resolved' do
        expect(Facter.value(:has_kubectl)).to be nil
      end
    end
  end

  describe ':kubernetes_version' do
    context 'on Linux system with kubectl present' do
      before do
        Facter.fact(:kernel).stubs(:value).returns('Linux')
        Facter.fact(:has_kubectl).stubs(:value).returns(true)
      end

      it 'returns client and server versions when kubectl returns valid JSON' do
        mock_json = {
          'clientVersion' => {'gitVersion' => 'v1.28.0'},
          'serverVersion' => {'gitVersion' => 'v1.28.0'}
        }.to_json
        Facter::Core::Execution.expects(:execute).with(
          '/usr/bin/kubectl version -o json',
          :on_fail => nil
        ).returns(mock_json)

        expect(Facter.value(:kubernetes_version)).to eq({
          'client' => 'v1.28.0',
          'server' => 'v1.28.0'
        })
      end

      it 'returns nil when kubectl command fails' do
        Facter::Core::Execution.expects(:execute).with(
          '/usr/bin/kubectl version -o json',
          :on_fail => nil
        ).returns(nil)

        expect(Facter.value(:kubernetes_version)).to be nil
      end

      it 'returns nil when JSON parsing fails' do
        Facter::Core::Execution.expects(:execute).returns('invalid json')

        expect(Facter.value(:kubernetes_version)).to be nil
      end

      it 'returns only client version if server version is missing' do
        mock_json = {
          'clientVersion' => {'gitVersion' => 'v1.28.0'}
        }.to_json
        Facter::Core::Execution.expects(:execute).returns(mock_json)

        expect(Facter.value(:kubernetes_version)).to eq({'client' => 'v1.28.0'})
      end
    end

    context 'on Linux system without kubectl' do
      before do
        Facter.fact(:kernel).stubs(:value).returns('Linux')
        Facter.fact(:has_kubectl).stubs(:value).returns(false)
      end

      it 'is not resolved' do
        expect(Facter.value(:kubernetes_version)).to be nil
      end
    end
  end
end

常见测试错误排查

如果还是报错,可以检查以下几点:

  • 测试文件中是否正确加载了你的fact文件路径
  • 是否在测试前调用了Facter.clear清除缓存
  • Mocha的mock语法是否正确(expects会验证方法是否被调用,stubs仅返回预设值,按需选择)
  • 有没有正确mockkernel的值,确保confine条件生效

内容的提问来源于stack exchange,提问作者jaxxstorm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:44:07