Testid peaksid aitama vältida rakenduste ebakindlust. Kuid aeg-ajalt võivad testid ise muutuda ketendavaks - ka kõige otsesemad. Siit saate teada, kuidas a Rubiin rööbastel rakenduse, mida toetab PostgreSQL, ja mida me avastasime.
Tahtsime kontrollida, et teatud äriloogika (mida kutsutakse meetodiga perform
) ei muuda a calendar
mudel (Calendar
eksemplar, Ruby on Rails ActiveRecordi mudeli klass), nii et kirjutasime:
let(:calendar) { create(:calendar) } specify do expect do perform # call the business action calendar.reload end .not_to change(calendar, :attributes) end
See möödus ühes arenduskeskkonnas (MacOS), kuid CI-s (Linux) oli see peaaegu alati ebaõnnestunud.
Õnneks õnnestus meil see reprodutseerida teises arenduskeskkonnas (Linux), kus see ebaõnnestus sõnumiga:
expected `Calendar#attributes` not to have changed, but did change from {'calendar_auth_id'=>8, 'created_at'=>2020-01-02 13:36:22.459149334 +0000, 'enabled'=>false, 'events_...t_sync_token'=>nil, 'title'=>nil, 'updated_at'=>2020-01-02 13:36:22.459149334 +0000, 'user_id'=>100} to { 'calendar_auth_id'=>8, 'created_at'=>2020-01-02 13:36:22.459149000 +0000, 'enabled'=>false, 'events_...t_sync_token'=>nil, 'title'=>nil, 'updated_at'=>2020-01-02 13:36:22.459149000 +0000, 'user_id'=>100}
Kas näete midagi kahtlast?
Lähemal uurimisel märkasime, et created_at
ja updated_at
ajatemplid muudeti expect
sees veidi plokk:
{'created_at'=>2020-01-02 13:36:22.459149334 +0000, 'updated_at'=>2020-01-02 13:36:22.459149334 +0000} {'created_at'=>2020-01-02 13:36:22.459149000 +0000, 'updated_at'=>2020-01-02 13:36:22.459149000 +0000}
Sekundite osaosa kärbiti nii, et 13:36:22.459149334
sai 13:36:22.459149000
.
Olime kindlad, et perform
ei värskendanud calendar
objekt, seega koostasime hüpoteesi, et ajatemplid kärbiti andmebaasi abil. Selle testimiseks kasutasime teadaolevalt kõige arenenumat silumisvõtet, s.t. paneb silumise :
let(:calendar) { create(:calendar) } specify do expect do puts 'before perform: #{calendar.created_at.to_f}' perform puts 'after perform: #{calendar.created_at.to_f}' calendar.reload puts 'after reload: #{calendar.created_at.to_f}' end .not_to change(calendar, :attributes) end
Kuid kärpimine ei olnud väljundis nähtav:
before perform: 1577983568.550754 after perform: 1577983568.550754 after reload: 1577983568.550754
See oli üsna üllatav - lisavarustus #created_at
oleks pidanud olema sama väärtus kui atribuudi räsiväärtus attributes['created_at']
Et olla kindel, et printisime väites kasutatud sama väärtust, muutsime juurdepääsu meetodit created_at
Aksessori calendar.created_at.to_f
kasutamise asemel läksime üle selle otsimisele otse atribuutide räsist: calendar.attributes['created_at'].to_f
. Meie kahtlused calendar.reload
suhtes kinnitati!
before perform: 1577985089.0547702 after perform: 1577985089.0547702 after reload: 1577985089.05477
Nagu näete, helistades perform
ei muutnud created_at
, kuid reload
tegi.
Veendumaks, et muudatus ei toimu mõnes muus calendar
eksemplaris ja siis olles päästetud, tegime veel ühe katse. Laadisime uuesti calendar
enne testi:
let(:calendar) { create(:calendar).reload } specify do expect do perform calendar.reload end .not_to change(calendar, :attributes) end
See muutis testi roheliseks.
Teades, et andmebaas kärbib meie ajatemplid ja ebaõnnestub testide tegemine, otsustasime kärpimise ära hoida. Lõime DateTime
objekt ja ümardas selle tervete sekunditeni. Seejärel kasutasime seda objekti Railsi aktiivse kirje ajatemplite selgesõnaliseks määramiseks. See muudatus fikseeris ja stabiliseeris testid:
let(:time) { 1.day.ago.round } let(:calendar) { create(:calendar, created_at: time, updated_at: time) } specify do expect do perform calendar.reload end .not_to change(calendar, :attributes) end
Miks see juhtus? Aktiivse kirje ajatemplid on rööbaste poolt seatud ’ActiveRecord::Timestamp
moodul kasutades Time.now
. Time
täpsus on OS-st sõltuv ja nagu dokumentides öeldakse, võib sisaldada murdosa sekundeid .
Testisime Time.now
eraldusvõime MacOS-is ja Linuxis koos skriptiga, mis loeb osade pikkuste sagedusi:
pry> 10000.times.map { Time.now.to_f.to_s.match(/.(d+)/)[1].size }.group_bya.map [k, v.count].to_h # MacOS => {6=>6581, 7=>2682, 5=>662, 4=>67, 3=>7, 2=>1} # Linux => {6=>2399, 7=>7300, 5=>266, 4=>32, 3=>3}
Nagu näete, oli umbes 70% Linuxi ajatemplitest pärast kümnendkohtade täpsust seitse numbrit, MacOS-is ainult 25%. See on põhjus, miks testid läbisid suurema osa ajast MacOS-is ja ebaõnnestusid enamasti Linuxis. Võib-olla olete märganud, et testväljundil oli üheksakohaline täpsus - seda seetõttu, et RSpec kasutab Time#nsec
ajaväljundi vormindamiseks.
Kui Railsi mudelid salvestatakse andmebaasi, salvestatakse kõik nende ajatemplid PostgreSQL-i tüüpi nimega timestamp without time zone
, mis on mikrosekundiline eraldusvõime - st kuus numbrit pärast koma. Niisiis, kui 1577987974.6472975
saadetakse PostgreSQL-i, see kärbib murdosa viimase numbri ja salvestab selle asemel 1577987974.647297
Ikka on küsimus, miks calendar.created_at
ei laaditud uuesti, kui helistasime calendar.reload
, kuigi calendar.attributes['created_at']
laaditi uuesti.
Samuti on Time
täpsuskatse on natuke üllatav. Eeldasime, et MacOS-is on maksimaalne täpsus kuus. Me ei tea, miks see nii on mõnikord on seitse numbrit. Mis üllatas meid rohkem, oli viimaste numbrite väärtuse jaotus:
pry> 10000.times.map { Time.now}.map t.to_f.to_s.match(/.(d+)/)[1] .select.group_bye.map [k, v.size].to_h # MacOS => {'9'=>536, '1'=>555, '2'=>778, '8'=>807} # Linux => {'5'=>981, '1'=>311, '3'=>1039, '9'=>309, '8'=>989, '6'=>1031, '2'=>979, '7'=>966, '4'=>978}
Nagu näete, on MacOS-i seitsmes number alati 1, 2, 8 või 9.
Kui teate vastust mõlemale küsimusele, jagage palun meiega selgitust.
Asjaolu, et Ruby on Rails ’ Aktiivne kirje Rakenduse poolel genereeritud ajatemplid võivad kahjustada ka seda, kui neid ajatempleid kasutatakse andmebaasi salvestatud sündmuste usaldusväärseks ja täpseks järjestamiseks. Kuna rakendusserveri kellad võidakse desünkroonida, järjestavad sündmused created_at
võivad ilmuda teistsuguses järjekorras kui need tegelikult aset leidsid. Saada usaldusväärsem käitumine , oleks parem lasta andmebaasiserveril ajatemplid käsitseda (nt PostgreSQL-id now()
).
See on aga lugu, mis on väärt teist artiklit.
Eriline tänu Gabriele Renzi selle artikli loomisel aitamise eest.
ActiveRecord on objektide-seoste kaardistamise teek, mille pakub Ruby on Rails. See võimaldab teil objekte andmebaasis säilitada, seejärel salvestatud andmeid hankida ja objekte kiirendada.
Active Record on ettevõtte arhitektuurimuster, mida kasutatakse relatsioonide andmebaasides mälus olevate objektide püsimiseks. Rakenduse Ruby on Rails rakenduses jälgib ActiveRecord muudatusi Railsi mudeli atribuutides. Mudeli salvestamisel saadab ActiveRecord muudatused koos vajalike ajatemplitega andmebaasi.
Vaikimisi salvestab ActiveRecord kaks ajatemplit: created_at (aeg, mil mudel esmakordselt salvestati) ja updated_at (aeg, mil mudel viimati salvestati). Need pakuvad põhilisi auditivõimalusi ja võimaldavad rakendustel sorteerida mudeleid alates uusimatest või viimati uuendatud mudelitest.
Ajatempel on PostgreSQL-i andmetüüp, mis salvestab nii kuupäeva kui ka kellaaega. Selle eraldusvõime ja täpsus on ühe mikrosekundiga, mis tähendab, et see hoiab sekundite murdosa kuni kuus numbrit.
Ruby on Rails võimaldab arendajatel kasutada enamikku populaarsetest andmebaasidest. Vaikimisi kasutab Ruby on Rails teeki ActiveRecord, mis toetab DB2, Firebird, FrontBase, MySQL, OpenBase, Oracle, PostgreSQL, SQLite, Microsoft SQL Server ja Sybase.