问题描述
对于我的红宝石测试者,我需要可预测的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
。