测试不可预测的功能
我目前正在忙于在 Ruby 中实现有趣的数据结构,并且遇到了测试没有可预测输出的函数的问题。我目前正在开发一个 Bloom Filter,为了完整起见,我已包含以下实现:
require "zlib"
class BloomFilter
def initialize(size=100, hash_count=3)
raise(ArgumentError, "negative or zero buffer size") if size <= 0
raise(ArgumentError, "negative or zero hash count") if hash_count <= 0
@size = size
@hash_count = hash_count
@buffer = Array.new(size, false)
end
def insert(element)
hash(element).each { |i| @buffer[i] = true}
end
def maybe_include?(element)
hash(element).map { |i| @buffer[i] }.inject(:&)
end
private :hash
def hash(element)
hashes = []
1.upto(@hash_count) do |i|
hashes << Zlib.crc32(element, i)
end
hashes.map { |h| h % @size }
end
end
一个布隆过滤器的一个问题是,它有可能通过错误地返回 true 来包含从未插入到过滤器中的元素,从而返回误报。
有时,过滤器的行为方式很容易测试:
b = BloomFilter.new(50, 5)
b.insert("hello")
puts b.maybe_include?("hello") # => true
puts b.maybe_include?("goodbye") # => false
但有时它会逆势而行,行为方式不可预测。 (我已经减小了此处缓冲区的大小,以便快速找到冲突。)
b = BloomFilter.new(5, 4)
b.insert("testing")
puts b.maybe_include?("testing") # => true
puts b.maybe_include?("not present") # => false
puts b.maybe_include?("false positive") # => true (oops)
所以突然间,我们得到了字符串“误报”,提供了...误报。我的问题是我们如何测试这个?
如果我们选择恰好适用于我们的测试的值,那么我 感觉测试变得太脆弱了。例如,如果我们改变 那么我们可能仍然有一个完全正确的 Bloom 由于我们选择的值,过滤器开始无法通过某些测试 测试原始实现。
我的第二个想法是测试过滤器的行为是否符合预期 通过检查我们是否大致获得了预期的数量 错误的 积极因素 通过改变散列函数的数量和大小 内部缓冲区。虽然这种方法可能会测试整体粗糙度 过滤器的正确性我担心它无法捕获 导致它报告个别情况的错误值(例如 false 我
我是否对上面两种测试方法的有效性过于悲观,或者我是否缺少一种测试类(例如输出不可预测的布隆过滤器)的方法?
I'm currently messing about with implementing interesting data structures in Ruby and have reached a problem with testing functions that do not have a predictable output. I'm currently working on a Bloom Filter that I have included the implementation of below for completeness:
require "zlib"
class BloomFilter
def initialize(size=100, hash_count=3)
raise(ArgumentError, "negative or zero buffer size") if size <= 0
raise(ArgumentError, "negative or zero hash count") if hash_count <= 0
@size = size
@hash_count = hash_count
@buffer = Array.new(size, false)
end
def insert(element)
hash(element).each { |i| @buffer[i] = true}
end
def maybe_include?(element)
hash(element).map { |i| @buffer[i] }.inject(:&)
end
private :hash
def hash(element)
hashes = []
1.upto(@hash_count) do |i|
hashes << Zlib.crc32(element, i)
end
hashes.map { |h| h % @size }
end
end
One of the problems with a Bloom Filter is that it has the possibility of returning false positives by falsely returning true for the inclusion of elements that have never been inserted into the filter.
Sometimes the filter behaves in a way that is easily testable:
b = BloomFilter.new(50, 5)
b.insert("hello")
puts b.maybe_include?("hello") # => true
puts b.maybe_include?("goodbye") # => false
However it sometimes bucks the trend and behaves in an unpredictable way. (I've reduced the size of the buffer here to find a conflict quickly.)
b = BloomFilter.new(5, 4)
b.insert("testing")
puts b.maybe_include?("testing") # => true
puts b.maybe_include?("not present") # => false
puts b.maybe_include?("false positive") # => true (oops)
So all of a sudden we have the string "false positive" providing a... false positive. My question is how can we test this?
If we choose values that just happen to work with our tests then I
feel like the tests become far too fragile. For example, if we change
the hashing function then we may still have a perfectly correct Bloom
Filter that starts to fail some tests because of the values we chose
to test the original implementation.My second thought was to test that the filter behaves in a expected
way by just checking that we get roughly the expected number of
false
positives
from it by varying the number of hash functions and size of the
internal buffer. While this approach may test the overall rough
correctness of the filter I worry that it will not be able to catch
bugs that cause it to report incorrect values for individual cases (such as false
negatives).
Am I being too pessimistic about the effectiveness of the two methods of testing it above or am I missing a way to test classes such as the Bloom Filter which the output is unpredictable?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
你是对的,选择恰好起作用的价值观是一个坏主意。然而,你的第二个想法还不错。
您应该始终能够测试布隆过滤器中应有的值是否存在。您可以随机生成多个字符串,并检查阈值数量是否为误报。这样,如果您更改哈希函数,您的单元测试仍然可以工作,并且仍然会报告过滤器具有可接受的误报率。
You're right that choosing values that just happen to work is a bad idea. However, your second idea is not so bad.
You should always be able to test that the values that should be in the bloom filter are there. You could randomly generate a number of strings, and check that a threshold amount are false positives. This way if you change the hash function your unit tests will still work and will still report that the filter has an acceptable false positive ratio.
测试是为了确认您的期望。如果您无法自己推断布隆过滤器将返回什么(考虑到脆弱性,正如您提到的),您就不能期望有这种期望。 (我发誓我不是想搞双关语:P)
我的第一个直觉是确认所有有趣的哈希算法的 N 个生成输入的误报百分比。这可以让您实现与手动执行这些测试一样多的自动化安全性。
为了实现这一目标,我建议对测试代码进行足够的分解,以便您可以简单地表达为:
<警告>未经验证的代码
Testing is about confirming your expectations. If you can't reason for yourself what the Bloom filter will return (considering the fragility, as you mentioned), you can't expect to have that expectation. (I swear I wasn't trying to make a pun :P)
My first gut feeling would be to confirm false positive percentage on N generated inputs on all the interesting hashing algorithms. This automates you as much security as you would have doing these tests manually.
To achieve this, I would recommend having the test code factored enough for you to express as simple as:
<warning> Unverified code </warning>
仅从布隆过滤器功能的描述就可以清楚地看出,测试误报是没有意义的。阳性测试的结果本质上是不确定的,因此您无法对其进行预期特定结果的测试。您唯一可以保证并测试的事情是:
Just from the description of what the Bloom filter does, it should be clear that it makes no sense to test for false positives. It's inherently undefined what the result of a positive test is, so you can't make tests for it that expect a certain result. The only things you can guarantee and hence test for are: