Testien on tarkoitus auttaa pitämään sovellukset hilseilemästä. Mutta joskus testeistä voi tulla hiutaleita - jopa suorimmatkin. Näin pääset ongelmalliseen testiin a Ruby on Rails PostgreSQL: n tukema sovellus ja mitä paljastimme.
Halusimme tarkistaa, että tietty liiketoimintalogiikka (jota kutsutaan menetelmällä perform
) ei muuta calendar
malli (esimerkki Calendar
, Ruby on Rails ActiveRecord -malliluokasta), joten kirjoitimme:
let(:calendar) { create(:calendar) } specify do expect do perform # call the business action calendar.reload end .not_to change(calendar, :attributes) end
Tämä oli ohi yhdessä kehitysympäristössä (MacOS), mutta se lähes aina epäonnistui CI: ssä (Linux).
Onneksi onnistuimme toistamaan sen toisessa kehitysympäristössä (Linux), jossa se epäonnistui viestillä:
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}
Näetkö jotain hämärää?
Tarkemmin tarkastellessamme huomasimme, että created_at
ja updated_at
aikaleimoja muutettiin hieman expect
: n sisällä lohko:
{'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}
Sekuntien murto-osa katkaistiin siten, että 13:36:22.459149334
tuli 13:36:22.459149000
.
Olimme varmoja, että perform
ei päivittänyt calendar
objektin, joten muodostimme hypoteesin, jonka mukaan tietokanta katkaisi aikaleimat. Tämän testaamiseksi käytimme edistyneintä tunnettua virheenkorjaustekniikkaa, ts. laittaa virheenkorjauksen :
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
Mutta katkaisu ei ollut näkyvissä tulosteessa:
before perform: 1577983568.550754 after perform: 1577983568.550754 after reload: 1577983568.550754
Tämä oli varsin yllättävää - lisävaruste #created_at
olisi pitänyt olla sama arvo kuin attribuutin hash-arvo attributes['created_at']
Varmistaaksemme, että tulostimme saman arvon kuin väitteessä, muutimme tapaa, jolla pääsimme created_at
Sen sijaan, että käyttäisimme lisävarustetta calendar.created_at.to_f
, vaihdoimme sen hakemiseen suoraan hash-määritteistä: calendar.attributes['created_at'].to_f
. Epäilyksemme calendar.reload
vahvistettiin!
before perform: 1577985089.0547702 after perform: 1577985089.0547702 after reload: 1577985089.05477
Kuten näette, soittaminen perform
ei muuttunut created_at
, mutta reload
teki.
Varmista, että muutosta ei tapahtunut toisessa calendar
-tapauksessa ja sitten pelastettuna teimme vielä yhden kokeen. Ladasimme uudelleen calendar
ennen testiä:
let(:calendar) { create(:calendar).reload } specify do expect do perform calendar.reload end .not_to change(calendar, :attributes) end
Se teki testistä vihreän.
Koska tiedämme, että tietokanta katkaisee aikaleimamme ja epäonnistuu testeissämme, päätimme estää katkaisun. Luomme DateTime
pyöristää sen kokonaisiin sekunteihin. Sitten käytimme tätä objektia asettamaan Railsin aktiivisen tietueen aikaleimat nimenomaisesti. Tämä muutos vahvisti ja vakautti testit:
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
Miksi näin tapahtui? Aktiivisen tietueen aikaleimat ovat kiskojen asettamat ’ActiveRecord::Timestamp
moduuli käyttämällä Time.now
. Time
tarkkuus on Käyttöjärjestelmästä riippuvainen ja kuten asiakirjoissa todetaan, voi sisältää murto-sekunteja .
Testasimme Time.now
päätöslauselma MacOS: ssa ja Linuxissa komentosarjalla, joka laskee murto-osan pituuksien taajuudet:
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}
Kuten näette, noin 70%: lla Linuxin aikaleimoista oli tarkkuus seitsemän numeroa desimaalin jälkeen, kun taas MacOS: ssa vain 25%. Tästä syystä testit läpäisivät suurimman osan ajasta MacOS: ssä ja epäonnistuivat suurimman osan ajasta Linuxissa. Olet ehkä huomannut, että testilähdöllä oli yhdeksän numeron tarkkuus - se johtuu RSpecista käyttää Time#nsec
muotoilla aikalähtö.
Kun Rails-mallit tallennetaan tietokantaan, kaikki niiden aikaleimat tallennetaan PostgreSQL: n tyypillä nimeltä timestamp without time zone
, joka on mikrosekunnin tarkkuus - eli kuusi numeroa desimaalin jälkeen. Joten kun 1577987974.6472975
lähetetään PostgreSQL: ään, se katkaisee murto-osan viimeisen numeron ja tallentaa sen sijaan 1577987974.647297
On edelleen kysymys miksi calendar.created_at
ei ladattu uudelleen, kun soitimme calendar.reload
, vaikka calendar.attributes['created_at']
ladattiin uudelleen.
Myös tulokset Time
tarkkuustesti ovat hieman yllättäviä. Odotimme, että MacOS: lla suurin tarkkuus on kuusi. Emme tiedä miksi joskus siinä on seitsemän numeroa. Yllätti meidät enemmän viimeisten numeroiden arvon jakautuminen:
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}
Kuten näette, MacOS: n seitsemäs numero on aina 1, 2, 8 tai 9.
Jos tiedät vastauksen jompaankumpaan näistä kysymyksistä, jaa selitys meille.
Se, että Ruby on Rails ” Aktiivinen ennätys Aikaleimat luodaan sovelluspuolelle voivat myös vahingoittaa, kun näitä aikaleimoja käytetään tietokantaan tallennettujen tapahtumien luotettavaan ja tarkkaan järjestämiseen. Koska sovelluspalvelimen kellot voidaan synkronoida, tapahtumat järjestetään created_at
voivat näkyä eri järjestyksessä kuin ne todellisuudessa tapahtuivat. Saada luotettavampi käyttäytyminen , olisi parempi antaa tietokantapalvelimen hoitaa aikaleimat (esim. PostgreSQL: t now()
).
Se on kuitenkin toisen artikkelin arvoinen tarina.
Erityiskiitokset Gabriele Renzi tämän artikkelin luomisessa.
ActiveRecord on objektisuhdekartoituskirjasto, jonka tarjoaa Ruby on Rails. Sen avulla voit säilyttää objekteja tietokannassa, hakea sitten tallennettuja tietoja ja välittää objekteja.
Active Record on yritysarkkitehtuurimalli, jota käytetään muistin sisäisten objektien säilyttämiseen relaatiotietokannoissa. Ruby on Rails -toteutuksessa ActiveRecord seuraa muutoksia Rails-mallin määritteisiin. Kun malli on tallennettu, ActiveRecord lähettää muutokset ja tarvittavat aikaleimat tietokantaan.
ActiveRecord tallentaa oletusarvoisesti kaksi aikaleimaa: created_at (aika, jolloin malli tallennettiin ensimmäisen kerran) ja updated_at (aika, jolloin malli viimeksi tallennettiin). Ne tarjoavat perustarkastusominaisuudet ja antavat sovellusten lajitella malleja uusimmista tai viimeisimmistä päivitetyistä.
Aikaleima on PostgreSQL-tietotyyppi, joka tallentaa sekä päivämäärän että kellonajan. Sillä on yhden mikrosekunnin tarkkuus ja tarkkuus, mikä tarkoittaa, että se pitää sekuntien murto-osan jopa kuuteen numeroon.
Ruby on Rails antaa kehittäjille mahdollisuuden käyttää suurinta osaa suosituista tietokannoista. Oletuksena Ruby on Rails käyttää ActiveRecord-kirjastoa, joka tukee DB2, Firebird, FrontBase, MySQL, OpenBase, Oracle, PostgreSQL, SQLite, Microsoft SQL Server ja Sybase.