生成有效的确定性UUID进行测试

问题描述

对于我的红宝石测试者,我需要可预测的UUID。我知道,UUID本质上是随机的且不确定的,这很好。但是在测试套件中,拥有可以通过固定装置,数据助手,种子等重用的UUID会很有用。

我现在有一个幼稚的实现,很容易导致无效的UUID:

def fake_uuid(character = "x")
  [8,4,12].map { |length| character * length }.join("-")
end

fake_uuid('a') => "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" # This is valid
fake_uuid('z') => "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz" # This is invalid,not hex.

我显然可以添加检查,只允许a-f,0-9作为输入。一种替代方法是,对预先生成的UUID列表进行编码,然后根据参数选择一个

但是我想知道,还有没有更好的方法? UUIDv5可以为此工作吗?是否可以调用SecureRandom.uuid以使其返回相同的UUID(用于线程或会话)? 需要额外的宝石吗?还是我最接近的方法

不需要由所有相同的字符组成。
具有一定的可读性是一个很大的优点,但不是必需的。这样,您可以例如确保Company的UUID为cccccccc-cccc-cccc-cccc-cccccccccccc,而其Employee的UUID为eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee

解决方法

我知道UUID本质上是随机且不确定的,这很好。

这个假设是错误的。

有5个版本的UUID:

  • 版本1和版本2基于MAC地址和日期时间,因此从理论上讲它将在同一时间在同一台计算机上提供相同的UUID方面具有确定性。
  • 第3版和第5版基于名称空间和名称,因此具有完全确定性。
  • 版本4是随机的。

因此,如果您使用版本3或版本5 UUID,它们将是完全确定的。

,

UUID使用两位数字表示其格式:(实际上只是一些数字位)

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
              ^    ^
        version    variant

以下模式表示version 4(M = 4),变体1(N = 8),其简单表示“随机字节”:

xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx

您可以将其用作模板,以根据序列号生成伪造(但有效)的UUID :(如注释中所建议)

def fake_uuid(n)
  '00000000-0000-4000-8000-%012x' % n
end

fake_uuid(1) #=> "00000000-0000-4000-8000-000000000001"
fake_uuid(2) #=> "00000000-0000-4000-8000-000000000002"
fake_uuid(3) #=> "00000000-0000-4000-8000-000000000003"

具有一定的可读性是一个很大的优点...

有大量未使用的字段/数字可添加更多数据:

def fake_uuid(klass,n)
  k = { Company => 1,Employee => 2 }.fetch(klass,0)

  '%08x-0000-4000-8000-%012x' % [k,n]
end

fake_uuid(Company,1)   #=> "00000001-0000-4000-8000-000000000001"
fake_uuid(Company,2)   #=> "00000001-0000-4000-8000-000000000002"

fake_uuid(Employee,1)  #=> "00000002-0000-4000-8000-000000000001"
fake_uuid(Employee,2)  #=> "00000002-0000-4000-8000-000000000002"

#                            ^^^^^^^^                ^^^^^^^^^^^^
#                              class                   sequence
,

尽可能考虑依赖注入和工厂

您要尝试执行的操作似乎是一种测试反模式。从理论上讲,您可以通过将Version-1 UUIDs与预定义的MAC地址配合使用,并使用timecop这样的gem来创建确定的时间,从而实现所需的功能,但是对于我可以使用的任何实际用例,这可能都是不合理的想象。

相反,您应该使用工厂而不是夹具进行测试,或者创建允许直接注入测试输入和/或输出值的方法。例如:

# some UUID-related method under test
def do_something_with(uuid=nil)
  # fetch the uuid the way you would if not injected
  uuid ||= gets.chomp
  uuid.tr '3','4'
end
  
# write your tests to validate pre-defined input and
# output values
input_value  = '01957E2E-B3BA-4A46-BC4D-00615BE630E3'
output_value = '01957E2E-B4BA-4A46-BC4D-00615BE640E4'

# validate the expected transformation
do_something_with(input_value) == output_value

无论是使用数据库还是使用像RSpec这样的测试DSL,这种方法的结果都应该相同,因为您要定义两个值。由于TDD / BDD不应测试核心功能,除非您实际尝试测试一些自定义UUID生成器,否则此方法应该可以做到。如果您正在滚动自己的生成器,则仍然可以使用相同的方法来注入诸如MAC地址,日期/时间或用于生成确定性UUID的其他因素之类的参数。

其他方法可能包括生成一组值(例如,为数据库创建种子),然后在完成测试后回滚或截断数据库。 database_cleaner宝石是执行此操作的必备条件,但是您的原始帖子并没有真正证明附加的复杂性。我在这里提到它主要是为了指出大多数情况下都有固定装置/工厂解决方案,它们仍然允许您遵循相同的基本注入或依赖可预测数据的模式。

,

如果您使用的是driver.get("https://testocolo.forumcommunity.net") #First click,working driver.find_element_by_xpath('//a[@href="'+"/?f=9087616"+'"]').click() try : element = WebDriverWait(driver,2).until( EC.presence_of_element_located(By.XPATH,'//a[@href="'+"/?t=61904616"+'"]') ) element.click() except : print("NO") ,则可以将<a href="/?t=61904616" title="discussione inviata il: 28/10/2020,19:58">Brotha</a> 的返回值存根。

rspec

每次在SecureRandom.uuid的上下文中调用context "my example context" do let(:expected_uuid) { "709ab60d-3c5f-48d8-ac55-dc6b8f4f85bf" } before do allow(SecureRandom).to receive(:uuid).and_return(expected_uuid) end it "uses the expected uuid" do # your spec end end 时,它将返回expected_guid