使用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仅返回预设值,按需选择) - 有没有正确mock
kernel的值,确保confine条件生效
内容的提问来源于stack exchange,提问作者jaxxstorm
相关产品推荐
相关产品推荐

