Kuigi Codest on peamiselt Ruby pood, on üks paljudest projektidest, mida me ehitame, JavaScript. See on kliendipoolne raamatukogu, mis töötab üsna keerulises keskkonnas: see peab toetama peaaegu kõiki olemasolevaid brausereid, sealhulgas väga vanu, ning lisaks sellele suhtleb see veel hulga väliste skriptide ja teenustega. See on tohutult lõbus.
Kummaline juhtum mittekomplektsete sõltuvuste puhul
Ülaltoodud nõuetega kaasneb terve rida väljakutseid, mida tavaliselt kliendipoolses süsteemis ei esine. projektja üks nende küsimuste klass on seotud testimisega. Loomulikult on meil laitmatu hulk ühikteste ja me jooksutame neid oma CI/CD-keskkonnas väga suure hulga brauserite / operatsioonisüsteemide kombinatsioonide vastu, kuid see üksi ei uuri kõike, mis võib valesti minna.
Tulenevalt ökosüsteemi üldisest arhitektuurist, milles me tegutseme, sõltume sellest, et mõned välised raamatukogud laaditakse koos meie omadega - see tähendab, et me ei pakenda neid koos meie kood; me ei saa ja selle vastu pole midagi teha. See kujutab endast huvitavat väljakutset, sest need raamatukogud:
- ei pruugi olla isegi olemas - kui keegi segab meie raamatukogu rakendamist,
- võib olla olemas, kuid vales/mittesobivates versioonides,
- võib olla muudetud mõne muu koodiga, mis on konkreetses rakenduses kaasas.
See näitab selgelt, miks ühiktestidest ei piisa: nad testivad reaalsest maailmast isoleeritult. Ütleme, et me mockime mõne välise raamatukogu avaliku API mingi osa, tuginedes sellele, mida me oleme avastanud selle dokumentides, ja käivitame selle vastu ühiktesti. Mida see tõestab?
Teil võib tekkida kiusatus öelda, et "see tähendab, et see töötab koos välise raamatukogu APIga", kuid te oleksite - kahjuks - valesti. See tähendab ainult seda, et see suhtleb korrektselt välise raamatukogu avaliku API alamhulgaga, ja isegi siis ainult selle versiooniga, mida me oleme jäljendanud.
Mis siis, kui raamatukogu muutub sõna otseses mõttes meie alt ära? Mis siis, kui see saab - seal looduses - mingeid kummalisi vastuseid, mis panevad selle sattuma teistsugusele, dokumenteerimata koodipolgule? Kas me saame selle eest üldse kaitsta?
Mõistlik kaitse
Mitte 100%, ei - keskkond on selleks liiga keeruline. Kuid me saame olla mõistlikult kindlad, et kõik töötab nii, nagu see peaks toimima, kasutades mõningaid üldistatud näiteid selle kohta, mis meie koodiga võib looduses juhtuda: me saame teha integratsioonitestimist. Ühiktestid tagavad, et meie kood töötab sisemiselt korralikult, ja integratsioonitestid peavad tagama, et me "räägime" korralikult raamatukogudega, mida me ei saa kontrollida. Ja ka mitte nende tüvedega - tegelike, elusate raamatukogudega.
Me võiksime lihtsalt kasutada ühte olemasolevatest integratsioonitesti raamistikest, et JavaScript, ehitame lihtsa HTML-lehe, esitame mõned kutsed meie raamatukogule ja kaugeleulatuvatele raamatukogudele ning anname sellele korraliku treeningu. Me ei taha aga üle ujutada ühtegi kaugteenuse lõpp-punkti meie CI/CD-keskkonna poolt genereeritud üleskutsetega. See segaks mõningaid statistilisi andmeid, võib-olla rikuks mõned asjad, ja - viimaks, kuid mitte vähemtähtsaks - me ei oleks väga tore, kui me teeksime kellegi toodangu meie testide osaks.
Aga kas integratsioonitestimine oli üldse võimalik? Kuna Ruby on meie esimene ja peamine armastus, siis toetusime oma teadmistele ja hakkasime mõtlema, kuidas me tavaliselt Ruby projektides kaugteenustega integratsioonitestimist teeme. Me võiksime kasutada midagi sellist nagu vcr gem, et salvestada kord toimuvat, ja siis jätkata selle taasesitamist meie testidele, kui see on vajalik.
Sisestage proxy
Vcr saavutab selle sisemiselt taotluste vahendamise teel. See oli meie a-ha! hetk. Me pidime iga päringu, mis ei tohiks "päris" internetis midagi tabada, proxy'ga edasi suunama mõnele stubed responses'ile. Siis antakse need sissetulevad andmed edasi välisele raamatukogule ja meie kood töötab nagu tavaliselt.
Kui prototüüpimine tundub keeruline, kasutame sageli Ruby't kui aja kokkuhoiu meetodit. Otsustasime teha meie JavaScript jaoks prototüübi testvaljuse Ruby's, et näha, kui hästi proxy idee töötab, enne kui võtame kohustuse ehitada midagi keerulisemat (võimalik, et) JavaScript's. See osutus üllatavalt lihtsaks. Tegelikult on see nii lihtne, et me ehitame selle artikli raames üheskoos ühe 🙂 .
Valgus, kaamera... oota, me unustasime rekvisiidid!
Loomulikult ei tegele me "päris asjaga" - isegi natuke sellest, mida me ehitame, selgitamine ületab kaugelt ühe blogipostituse ulatuse. Me saame ehitada midagi kiiret ja lihtsat, mis seisab kõnealuste raamatukogude eest ja siis keskenduda rohkem Ruby osale.
Kõigepealt on meil vaja midagi, mis asendaks välist raamatukogu, millega me tegeleme. Meil on vaja, et see näitaks paari käitumist: see peaks võtma ühendust välise teenusega, kiirgama siin ja seal sündmust ning - mis kõige tähtsam - ei peaks olema ehitatud lihtsat integreerimist silmas pidades 🙂 .
Siin on see, mida me kasutame:
/* global XMLHttpRequest, Event */
const URL = 'https://poloniex.com/public/?command=returnTicker'
const METHOD = 'GET'
module.exports = {
fetch: function () {
var req = new XMLHttpRequest()
req.responseType = 'json'
req.open(METHOD, URL, true)
var doneEvent = new Event('example:fetched')
req.onreadystatechange = function (aEvt) {
if (req.readyState === 4) {
if (req.status === 200) {
this.data = req.response
} else {
this.error = true
}
window.dispatchEvent(doneEvent)
}
}.bind(this)
req.send(null)
},
error: false,
data: {}
}
Sa märkad, et see kutsub avatud API-d mõnede andmete jaoks - antud juhul mõned krüptovaluuta vahetuskursid, kuna see on tänapäeval kogu raevu See API ei eksponeeri liivakasti ja see on rate-limited, mis teeb sellest suurepärase näite millestki, mida ei tohiks tegelikult testides tabada.
Te võite märgata, et see on tegelikult NPM-ühilduv moodul, samas kui ma vihjasin sellele, et skript, millega me tavaliselt tegeleme, ei ole NPM-is saadaval, et seda saaks hõlpsasti komplekteerida. Selle demonstratsiooni jaoks piisab sellest, et see näitab teatud käitumist, ja ma tahaksin siin pigem lihtsustada seletamist liigse lihtsustamise hinnaga.
Näitlejate kutsumine
Nüüd on meil vaja ka midagi, mis asendaks meie raamatukogu. Jällegi hoiame nõuded lihtsad: see peab kutsuma meie "välist" raamatukogu ja tegema midagi väljundiga. Selleks, et hoida "testitav" osa lihtsana, laseme tal teha ka kahekordset logimist: nii konsooli, mida on natuke raskem lugeda spetsifikatsioonides, kui ka globaalselt saadaval olevasse massiivi.
window.remote = require('remote-calling-example')
window.failedMiserably = true
window.logs = []
function log (message) {
window.logs.push(message)
console.log(message)
}
window.addEventListener('example:fetched', function () {
if (window.remote.error) {
log("[EXAMPLE] Remote fetch failed')
window.failedMiserably = true
} else {
log('[EXAMPLE] Remote fetch successful')
log([EXAMPLE] BTC to ETH: ${window.remote.data.BTC_ETH.last})
}
})
window.remote.fetch()
Ma hoian ka käitumise meelega hämmastavalt lihtsana. Nii nagu see on, on ainult kaks tegelikult huvitavat koodirada, mille jaoks spetsifitseerida, nii et me ei jää spetsifikaatide laviini alla, kui me ehitamise käigus edasi liigume.
See kõik lihtsalt klapib kokku
Viskame üles lihtsa HTML-lehe:
<code> <!DOCTYPE html>
<html>
<head>
<title>Näidisleht</title>
<script type="text/javascript" src="./index.js"></script>
</head>
<body></body>
</html>
Selle demo tarbeks ühendame oma HTML ja JavaScript koos Pakett, väga lihtne veebirakendus komplekteerija. Mulle meeldib Parcel väga sellistel puhkudel, kui ma viskan kokku kiire näite või häkkisin mingi klassiidee. Kui teed midagi nii lihtsat, et Webpacki konfigureerimine võtaks rohkem aega kui soovitud koodi kirjutamine, on see parim.
See on ka piisavalt tagasihoidlik, et kui ma tahan vahetada midagi, mis on natuke rohkem lahingukatsetatud, ei pea ma tegema peaaegu mingit tagasilööki Parcelist, mida ei saa öelda Webpacki kohta. Märkus ettevaatust, aga - Parcel on raske arengu ja probleemid võivad ja ilmnevad; mul on olnud probleem, kus transpiled JavaScript väljund oli kehtetu vanemal Node.js. Lõpptulemus: ärge tehke seda veel oma tootmisprotsessi osaks, kuid proovige seda sellegipoolest.
Integratsiooni võimsuse rakendamine
Nüüd saame koostada oma testivalmisprogrammi.
Spetsifikaadi raamistiku enda jaoks kasutasime rspec. Arenduskeskkondades testime, kasutades tegelikku, mitteheadless Chrome'i - selle käivitamise ja kontrollimise ülesanne on langenud watir (ja selle usaldusväärne abiline watir-rspec). Meie proxy jaoks oleme kutsunud Puffing Billy ja rack peole. Lõpuks tahame oma JavaScript ehitust iga kord uuesti käivitada, kui käivitame spetsifikatsioonid, ja see saavutatakse järgmiselt. kokaiin.
See on terve hulk liikuvaid osi ja seega on meie spetsifitseerimise abimees isegi selles lihtsas näites mõnevõrra... kaasatud. Vaatame seda ja võtame selle lahti.
Dir['./spec/support/*/.rb'].each { |f| require f }
TEST_LOGGER = Logger.new(STDOUT)
RSpec.configure do |config|
config.before(:suite) { Cocaine::CommandLine.new('npm', 'run build', logger: TEST_LOGGER).run }
config.include Watir::RSpec::Helper
config.include Watir::RSpec::Matchers
config.include ProxySupport
config.order = :random
BrowserSupport.configure(config)
end
Billy.configure do |c|
c.cache = false
c.cacherequestheaders = false
c.persistcache = false
c.recordstubrequests = true
c.logger = Logger.new(File.expandpath('../log/billy.log', FILE))
end
Enne kogu komplekti käivitame oma kohandatud ehituskäsu läbi kokaiini. See konstant TEST_LOGGER võib olla natuke üle jõu käiv, kuid me ei ole siin väga mures objektide arvu pärast. Loomulikult käivitame specs'id juhuslikus järjekorras ja meil on vaja lisada kõik watir-rspec'i head asjad. Samuti peame seadistama Billy nii, et see ei teeks vahemälu, kuid ulatuslik logimine toimub spec/log/billy.log
. Kui te ei tea, kas päring on tegelikult peatunud või tabab live-serverit (hups!), siis on see logi puhas kuld.
Olen kindel, et teie terav silm on juba märganud ProxySupport ja BrowserSupport. Sa võid arvata, et meie kohandatud maiuspalad istuvad seal... ja sul oleks täpselt õigus! Vaatame kõigepealt, mida BrowserSupport teeb.
Brauser, kontrollitud
Esmalt tutvustame TempBrowser
:
klass TempBrowser
def get
@browser ||= Watir::Browser.new(web_driver)
end
def kill
@browser.close if @browser
@browser = nil
end
private
def web_driver
Selenium::WebDriver.for(:chrome, options: options)
end
def options
Selenium::WebDriver::Chrome::Options.new.tap do |options|
options.addargument '--auto-open-devtools-for-tabs'
options.addargument "--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}"
end
end
end
Töötades tagasi läbi kõnepuu, näeme, et me seadistame Seleniumi brauseri valikute komplekti Chrome'ile. Üks valikuid, mida me sinna edastame, on meie seadistuses oluline: see annab Chrome'i instantsile korralduse edastada kõik meie Puffing Billy instantsi kaudu. Teine valik on lihtsalt tore - iga instants, mida me käivitame, mis ei ole peata avanevad kontrollivahendid automaatselt. See säästab meid lugematul hulgal Cmd+Alt+I'd päevas 😉.
Pärast seda, kui oleme seadistanud brauseri nende valikutega, edastame selle Watirile ja see ongi enam-vähem kõik. Veebileht tappa
meetod on natuke suhkrut, mis võimaldab meil vajaduse korral draiverit korduvalt peatada ja taaskäivitada, ilma et TempBrowser'i instantsi ära visata.
Nüüd saame anda meie rspec näidetele paar supervõimet. Esiteks, me saame nutikaid brauser
abimeetod, mille ümber meie spetsifikatsioonid enamasti keerlevad. Me saame kasutada ka praktilist meetodit, et taaskäivitada brauser konkreetse näite jaoks, kui me teeme midagi ülitundlikku. Loomulikult tahame ka brauseri pärast testikomplekti lõpetamist tappa, sest me ei taha mitte mingil juhul, et Chrome'i instantsid jääksid püsima - meie RAM-i huvides.
moodul BrowserSupport
def self.browser
@browser ||= TempBrowser.new
end
def self.configure(config)
config.around(:iga) do |example|
BrowserSupport.browser.kill if example.metadata[:clean]
@browser = BrowserSupport.browser.get
@browser.cookies.clear
@browser.driver.manage.timeouts.implicit_wait = 30
example.run
end
config.after(:suite) do
BrowserSupport.browser.kill
end
end
end
Proxy ühendamine
Meil on loodud brauser ja spetsiifilised abimehed ning me oleme valmis alustama päringute edastamist meie proksile. Aga oodake, me ei ole seda veel seadistanud! Me võiksime iga näite jaoks korduvaid kutsungeid Billyle paugutada, kuid parem on hankida endale paar abimeetodit ja säästa paar tuhat klahvivajutust. See ongi see, mida ProxySupport
teeb.
See, mida me kasutame oma testseadistuses, on veidi keerulisem, kuid siin on üldine idee:
frozenstringliteral: true
nõuda 'json'
moodul ProxySupport
HEADERS = {
'Access-Control-Allow-Methods' => 'GET',
'Access-Control-Allow-Headers' => 'X-Requested-With, X-Prototype-Version, Content-Type',
'Access-Control-Allow-Origin' => '*'
}.freeze
def stubjson(url, file)
Billy.proxy.stub(url).andreturn({
body: open(file).read,
code: 200,
headers: HEADERS.dup
})
end
def stubstatus(url, status)
Billy.proxy.stub(url).andreturn({
body: '',
code: status,
headers: HEADERS.dup
})
end
def stubpage(url, file)
Billy.proxy.stub(url).andreturn(
body: open(file).read,
content_type: 'text/html',
code: 200
)
end
def stubjs(url, file)
Billy.proxy.stub(url).andreturn(
body: open(file).read,
content_type: 'application/javascript',
code: 200
)
end
end
Me võime torkida:
- HTML-lehe taotlused - meie peamise "mänguväljaku" lehe jaoks,
- JS taotlused - meie komplekteeritud raamatukogu teenindamiseks,
- JSON-päringud - taotluse esitamiseks kaugele API-le,
- ja lihtsalt "mis tahes" taotlus, mille puhul meid huvitab ainult konkreetse, mitte-200 HTTP-vastuse tagastamine.
See sobib meie lihtsa näite jaoks hästi. Näidetest rääkides - me peaksime looma paar!
Hea külje testimine
Me peame kõigepealt ühendama paar "marsruuti" meie proxy jaoks:
let(:pageurl) { 'http://myfancypage.local/index.html' }
let(:jsurl) { 'http://myfancypage.local/dist/remote-caller-example.js' }
let(:pagepath) { './dist/index.html' }
let(:jspath) { './dist/remote-caller-example.js' }
before do
stubpage pageurl, pagepath
stubjs jsurl, jspath
end
Tasub märkida, et rspec'i seisukohalt viitavad suhtelised teed siin projekti põhikataloogile, seega laadime oma HTML-i ja JS-i otse kataloogist dist
kataloogi - nagu on ehitatud Parcel'i poolt. Saate juba näha, kuidas need stub_*
abilised tulevad kasuks.
Samuti tasub märkida, et me paigutame oma "võltsitud" veebilehe kohta .local
TLD. Sel viisil ei tohiks meie kohalikust keskkonnast väljuda ühtegi väljapääsmatut taotlust, kui midagi läheb halvasti. Üldise tavana soovitaksin vähemalt mitte kasutada "päris" domeeninimesid stubides, kui see pole absoluutselt vajalik.
Teine märkus, mille me peaksime siinkohal tegema, on see, et me ei tohi ennast korrata. Kuna proxy marsruutimine muutub keerulisemaks, palju rohkemate teede ja URL-idega, on selle seadistuse väljavõtmine jagatud konteksti ja selle lihtne kaasamine vajaduse korral tõelist väärtust.
Nüüd saame täpsustada, kuidas meie "hea" tee peaks välja nägema:
kontekst 'õige vastusega' do
before do
stubjson %r{http://poloniex.com/public(.*)}, './spec/fixtures/remote.json'
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 2') }
end
it 'logib korralikud andmed' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXAMPLE] Remote fetch successful', '[EXAMPLE] BTC to ETH: 0.03619999'])
)
end
end
See on üsna lihtne, kas pole? Veel mõned seadistused siin - stubime JSON-vastuse kaug-API-st koos kinnitusega, läheme meie peamisele URLile ja siis... ootame.
Pikim ooteaeg
Ootused on viis, kuidas ümber käia piiranguga, millega me oleme Watiriga kokku puutunud - me ei saa usaldusväärselt oodata näiteks JavaScript sündmusi, seega peame natuke petma ja "ootama", kuni skriptid on liigutanud mõne objekti, millele meil on juurdepääs, meile huvipakkuvasse olekusse. Miinuseks on see, et kui seda olekut ei tule kunagi (näiteks vea tõttu), siis peame ootama, et watir waiter aeg maha võtaks. See ajab spec aega natuke üles. Spekter siiski usaldusväärselt ebaõnnestub.
Pärast seda, kui leht on "stabiliseerunud" meile huvipakkuvas olekus, saame sooritada veel mõned JavaScript lehekülje kontekstis. Siinkohal kutsume üles avalikku massiivi kirjutatud logid ja kontrollime, kas need on need, mida me ootasime.
Kõrvalmärkusena - siinkohal paistab kaugjuhtimispäringu stubbing tõesti silma. Vastus, mis konsooli logitakse, sõltub kaugjuhtimisplatvormi poolt tagastatud vahetuskursist, nii et me ei saaks usaldusväärselt testida logi sisu, kui see pidevalt muutuks. Loomulikult on võimalusi selle vältimiseks, kuid need ei ole väga elegantsed.
Halva haru testimine
Veel üks asi, mida testida: "ebaõnnestumise" haru.
kontekst 'ebaõnnestunud vastusega' do
before do
stubstatus %r{http://poloniex.com/public(.*)}, 404
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 1') }
end
it 'logide ebaõnnestumine' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXAMPLE] Remote fetch failed'])
)
end
end
See on väga sarnane ülaltooduga, erinevus seisneb selles, et me tagastame vastuseks HTTP-koodi 404 ja ootame teistsugust logi.
Käivitame nüüd meie spetsifikatsioonid.
% pakett exec rspec
Randomiseeritud seemnega 63792
I, [2017-12-21T14:26:08.680953 #7303] INFO -- : Command :: npm run build
Kaugkutsumine
õige vastusega
logib õiged andmed
ebaõnnestunud vastusega
logib ebaõnnestumise
Lõpetas 23,56 sekundiga (failide laadimine võttis aega 0,86547 sekundit).
2 näidet, 0 ebaõnnestumist
Woohoo!
Kokkuvõte
Me arutasime lühidalt, kuidas JavaScript saab integreerimist testida koos Ruby'ga. Kuigi algselt peeti seda pigem vahepealseks, oleme nüüd oma väikese prototüübiga üsna rahul. Loomulikult kaalume endiselt puhta JavaScript lahenduse loomist, kuid vahepeal on meil lihtne ja praktiline viis, kuidas reprodutseerida ja testida mõningaid väga keerulisi olukordi, millega oleme looduses kokku puutunud.
Kui te kaalute midagi sarnast ise ehitada, tuleb märkida, et see ei ole ilma piiranguteta. Näiteks kui teie testimine muutub väga AJAX-rohke, võtab Puffing Billy-l vastamine kaua aega. Samuti, kui teil on vaja mõned SSL-allikaid kasutada, on vaja veel veidi rohkem fiktsioone - vaadake watiri dokumentatsiooni, kui see on teie nõue. Kindlasti jätkame uurimist ja otsime parimaid viise meie unikaalse kasutusjuhtumi lahendamiseks - ja anname kindlasti ka teile teada, mida me välja leidsime.