Αν και η Codest είναι κατά κύριο λόγο ένα κατάστημα Ruby, ένα από τα πολλά έργα που κατασκευάζουμε είναι σε JavaScript. Πρόκειται για μια βιβλιοθήκη από την πλευρά του πελάτη, η οποία τρέχει σε ένα αρκετά δύσκολο περιβάλλον: πρέπει να υποστηρίζει σχεδόν κάθε browser που υπάρχει, συμπεριλαμβανομένων των πολύ παλιών, και επιπλέον αλληλεπιδρά με ένα σωρό εξωτερικά σενάρια και υπηρεσίες. Είναι πολύ διασκεδαστικό.
Η περίεργη περίπτωση των μη-δεσμευμένων εξαρτήσεων
Με τις παραπάνω απαιτήσεις έρχεται ένα σύνολο προκλήσεων που συνήθως δεν υπάρχουν σε ένα client-side έργο, και μια κατηγορία αυτών των θεμάτων έχει να κάνει με τις δοκιμές. Φυσικά, έχουμε ένα άψογο σύνολο δοκιμών μονάδας και τις εκτελούμε ενάντια σε ένα πολύ μεγάλο πλέγμα συνδυασμών προγραμμάτων περιήγησης / λειτουργικών συστημάτων στο περιβάλλον CI/CD, αλλά αυτό από μόνο του δεν διερευνά όλα όσα μπορεί να πάνε στραβά.
Λόγω της συνολικής αρχιτεκτονικής του οικοσυστήματος στο οποίο λειτουργούμε, εξαρτόμαστε από ορισμένες εξωτερικές βιβλιοθήκες που φορτώνονται μαζί με τις δικές μας - δηλαδή, δεν τις ενσωματώνουμε μαζί με τις δικές μας κωδικόςΔεν μπορούμε και δεν μπορούμε να κάνουμε τίποτα γι' αυτό. Αυτό αποτελεί μια ενδιαφέρουσα πρόκληση, επειδή αυτές οι βιβλιοθήκες:
- μπορεί να μην υπάρχει καν - αν κάποιος τα κάνει θάλασσα με την υλοποίηση της βιβλιοθήκης μας,
- μπορεί να υπάρχουν, αλλά σε λάθος/ασύμβατες εκδόσεις,
- μπορεί να έχει τροποποιηθεί από κάποιον άλλο κώδικα που είναι μαζί σε μια συγκεκριμένη εφαρμογή.
Αυτό δείχνει ξεκάθαρα γιατί οι δοκιμές μονάδας δεν αρκούν: ελέγχουν σε απομόνωση από τον πραγματικό κόσμο. Ας πούμε ότι διαμορφώνουμε κάποιο μέρος του δημόσιου API κάποιας εξωτερικής βιβλιοθήκης, με βάση αυτά που έχουμε ανακαλύψει στα έγγραφά της, και εκτελούμε ένα τεστ μονάδας ενάντια σε αυτό. Τι αποδεικνύει αυτό;
Μπορεί να μπείτε στον πειρασμό να πείτε "αυτό σημαίνει ότι λειτουργεί με το API της εξωτερικής βιβλιοθήκης", αλλά θα κάνετε - δυστυχώς - λάθος. Σημαίνει μόνο ότι αλληλεπιδρά σωστά με ένα υποσύνολο του δημόσιου API της εξωτερικής βιβλιοθήκης, και ακόμη και τότε μόνο με την έκδοση που έχουμε διαμορφώσει.
Τι θα συμβεί αν η βιβλιοθήκη αλλάξει κυριολεκτικά κάτω από τα πόδια μας; Τι γίνεται αν - εκεί έξω στην άγρια φύση - λάβει κάποιες περίεργες αντιδράσεις που την κάνουν να χτυπήσει ένα διαφορετικό, μη τεκμηριωμένο μονοπάτι κώδικα; Μπορούμε να προστατευτούμε από αυτό;
Εύλογη προστασία
Όχι 100%, όχι - το περιβάλλον είναι πολύ περίπλοκο για κάτι τέτοιο. Μπορούμε όμως να είμαστε αρκετά σίγουροι ότι όλα λειτουργούν όπως πρέπει με κάποια γενικευμένα παραδείγματα του τι μπορεί να συμβεί στον κώδικά μας στη φύση: μπορούμε να κάνουμε δοκιμές ολοκλήρωσης. Οι δοκιμές μονάδας διασφαλίζουν ότι ο κώδικάς μας τρέχει σωστά εσωτερικά, και οι δοκιμές ολοκλήρωσης πρέπει να διασφαλίζουν ότι "μιλάμε" σωστά με τις βιβλιοθήκες που δεν μπορούμε να ελέγξουμε. Και μάλιστα όχι με stubs αυτών - πραγματικές, ζωντανές βιβλιοθήκες.
Θα μπορούσαμε απλά να χρησιμοποιήσουμε ένα από τα διαθέσιμα πλαίσια δοκιμών ολοκλήρωσης για JavaScript, δημιουργήστε μια απλή σελίδα HTML, κάντε μερικές κλήσεις στη βιβλιοθήκη μας και στις απομακρυσμένες βιβλιοθήκες και δοκιμάστε την. Ωστόσο, δεν θέλουμε να κατακλύσουμε κανένα από τα τελικά σημεία των απομακρυσμένων υπηρεσιών με κλήσεις που δημιουργούνται από τα περιβάλλοντα CI/CD μας. Αυτό θα μπέρδευε κάποια στατιστικά στοιχεία, ενδεχομένως θα έσπαγε κάποια πράγματα, και - τελευταίο αλλά όχι λιγότερο σημαντικό - δεν θα ήμασταν πολύ ευγενικοί αν κάναμε την παραγωγή κάποιου να αποτελεί μέρος των δοκιμών μας.
Ήταν όμως δυνατή η δοκιμή ολοκλήρωσης για κάτι τόσο πολύπλοκο; Δεδομένου ότι η Ruby είναι η πρώτη και κύρια αγάπη μας, ανατρέξαμε στην τεχνογνωσία μας και αρχίσαμε να σκεφτόμαστε πώς κάνουμε συνήθως δοκιμές ολοκλήρωσης με απομακρυσμένες υπηρεσίες σε έργα Ruby. Θα μπορούσαμε να χρησιμοποιήσουμε κάτι σαν το vcr gem για να καταγράψουμε αυτό που συμβαίνει μία φορά, και στη συνέχεια να συνεχίσουμε να το αναπαράγουμε στις δοκιμές μας όποτε χρειαστεί.
Εισάγετε πληρεξούσιο
Εσωτερικά το vcr το επιτυγχάνει αυτό με την διαμεσολάβηση αιτήσεων. Αυτή ήταν η στιγμή του "α-χα!". Χρειαζόμασταν να στέλνουμε μεσολάβηση κάθε αίτηση που δεν θα έπρεπε να χτυπήσει τίποτα στο "πραγματικό" διαδίκτυο σε κάποιες υπολειπόμενες απαντήσεις. Στη συνέχεια, αυτά τα εισερχόμενα δεδομένα θα παραδίδονται στην εξωτερική βιβλιοθήκη και ο κώδικάς μας θα εκτελείται κανονικά.
Όταν δημιουργούμε πρωτότυπα για κάτι που φαίνεται περίπλοκο, συχνά καταφεύγουμε στη Ruby ως μέθοδο εξοικονόμησης χρόνου. Αποφασίσαμε να φτιάξουμε ένα πρωτότυπο δοκιμαστικό εργαλείο για το JavaScript μας στη Ruby για να δούμε πόσο καλά θα λειτουργήσει η ιδέα του proxy πριν δεσμευτούμε να φτιάξουμε κάτι πιο περίπλοκο σε (ενδεχομένως) JavaScript. Αποδείχθηκε ότι ήταν εκπληκτικά απλό. Στην πραγματικότητα είναι τόσο απλό που θα φτιάξουμε ένα τέτοιο σε αυτό το άρθρο μαζί 🙂 .
Φώτα, κάμερα... περιμένετε, ξεχάσαμε τα σκηνικά!
Φυσικά δεν θα ασχοληθούμε με το "πραγματικό πράγμα" - το να εξηγήσουμε έστω και λίγο από αυτό που κατασκευάζουμε ξεπερνάει κατά πολύ το πλαίσιο ενός blog post. Μπορούμε να φτιάξουμε κάτι γρήγορο και εύκολο να αντικαταστήσει τις εν λόγω βιβλιοθήκες και στη συνέχεια να επικεντρωθούμε περισσότερο στο κομμάτι της Ruby.
Πρώτον, χρειαζόμαστε κάτι για να αντικαταστήσουμε την εξωτερική βιβλιοθήκη με την οποία έχουμε να κάνουμε. Χρειαζόμαστε να επιδεικνύει μερικές συμπεριφορές: θα πρέπει να επικοινωνεί με μια εξωτερική υπηρεσία, να εκπέμπει ένα συμβάν εδώ κι εκεί, και κυρίως - να μην έχει κατασκευαστεί με γνώμονα την εύκολη ενσωμάτωση 🙂 .
Εδώ είναι αυτό που θα χρησιμοποιήσουμε:
/* 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: {}
}
Θα παρατηρήσετε ότι καλεί ένα ανοιχτό API για κάποια δεδομένα - σε αυτή την περίπτωση κάποιες συναλλαγματικές ισοτιμίες κρυπτονομισμάτων, αφού αυτό είναι το πιο δημοφιλές στις μέρες μας Αυτό το API δεν εκθέτει ένα sandbox και είναι περιορισμένο σε ρυθμό, γεγονός που το καθιστά ένα χαρακτηριστικό παράδειγμα για κάτι που δεν πρέπει να χτυπηθεί σε δοκιμές.
Ίσως παρατηρήσετε ότι αυτό είναι στην πραγματικότητα μια ενότητα συμβατή με το NPM, ενώ έχω υπαινιχθεί ότι το σενάριο με το οποίο συνήθως ασχολούμαστε δεν είναι διαθέσιμο στο NPM για εύκολη ομαδοποίηση. Για αυτή την επίδειξη αρκεί ότι παρουσιάζει συγκεκριμένη συμπεριφορά, και θα προτιμούσα να έχω ευκολία στην εξήγηση εδώ με το κόστος της υπεραπλούστευσης.
Πρόσκληση των ηθοποιών
Τώρα χρειαζόμαστε επίσης κάτι για να αντικαταστήσουμε τη βιβλιοθήκη μας. Και πάλι, θα κρατήσουμε τις απαιτήσεις απλές: πρέπει να καλεί την "εξωτερική" βιβλιοθήκη μας και να κάνει κάτι με την έξοδο. Για χάρη της απλότητας του "ελέγξιμου" μέρους, θα το βάλουμε επίσης να κάνει διπλή καταγραφή: τόσο στην κονσόλα, η οποία είναι λίγο πιο δύσκολο να διαβαστεί στις προδιαγραφές, όσο και σε έναν παγκόσμια διαθέσιμο πίνακα.
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('[ΠΑΡΑΔΕΙΓΜΑ] Απομακρυσμένη ανάκτηση απέτυχε')
window.failedMiserably = true
} else {
log('[ΠΑΡΑΔΕΙΓΜΑ] Remote fetch successful')
log([EXAMPLE] BTC to ETH: ${window.remote.data.BTC_ETH.last})
}
})
window.remote.fetch()
Επίσης, κρατάω τη συμπεριφορά απίστευτα απλή επίτηδες. Όπως είναι, υπάρχουν μόνο δύο πραγματικά ενδιαφέροντα μονοπάτια κώδικα για τα οποία πρέπει να γίνουν προδιαγραφές, έτσι ώστε να μην παρασυρθούμε από μια χιονοστιβάδα προδιαγραφών καθώς προχωράμε στην κατασκευή.
Όλα απλά κουμπώνουν μαζί
Θα ανεβάσουμε μια απλή σελίδα HTML:
<code> <!DOCTYPE html>
<html>
<head>
<title>Παράδειγμα σελίδας</title>
<script type="text/javascript" src="./index.js"></script>
</head>
<body></body>
</html>
Για τους σκοπούς αυτής της επίδειξης θα συνδυάσουμε την HTML και το JavaScript μαζί με Αγροτεμάχιο, ένα πολύ απλό διαδικτυακή εφαρμογή bundler. Μου αρέσει πολύ το Parcel για τέτοιες στιγμές, όταν προσπαθώ να φτιάξω ένα γρήγορο παράδειγμα ή να επεξεργαστώ μια ιδέα για μια τάξη. Όταν κάνετε κάτι τόσο απλό που η διαμόρφωση του Webpack θα έπαιρνε περισσότερο χρόνο από το να γράψετε τον κώδικα που θέλετε, είναι το καλύτερο.
Είναι επίσης αρκετά διακριτικό, ώστε όταν θέλω να μεταβώ σε κάτι που είναι λίγο πιο δοκιμασμένο στη μάχη, δεν χρειάζεται να κάνω σχεδόν κανένα πισωγύρισμα από το Parcel, κάτι που δεν μπορείτε να πείτε για το Webpack. Σημείωση προσοχής, ωστόσο - το Parcel βρίσκεται σε έντονη ανάπτυξη και τα ζητήματα μπορούν και θα παρουσιαστούν. είχα ένα ζήτημα όπου η μεταγλωττισμένη έξοδος JavaScript ήταν άκυρη σε ένα παλαιότερο Node.js. Η ουσία: μην το κάνετε μέρος του αγωγού παραγωγής σας ακόμα, αλλά δοκιμάστε το.
Αξιοποίηση της δύναμης της ολοκλήρωσης
Τώρα μπορούμε να κατασκευάσουμε το test harness μας.
Για το ίδιο το πλαίσιο προδιαγραφών, έχουμε χρησιμοποιήσει rspec. Σε περιβάλλοντα ανάπτυξης δοκιμάζουμε χρησιμοποιώντας πραγματικό, μη-headless Chrome - το έργο της λειτουργίας και του ελέγχου αυτού έχει ανατεθεί στην watir (και ο πιστός βοηθός του watir-rspec). Για τον πληρεξούσιό μας, προσκαλέσαμε Puffing Billy και rack στο πάρτι. Τέλος, θέλουμε να επαναλαμβάνουμε την κατασκευή του JavaScript κάθε φορά που εκτελούμε τις προδιαγραφές, και αυτό επιτυγχάνεται με την εντολή κοκαΐνη.
Αυτό είναι ένα σωρό κινούμενα μέρη, και έτσι ο βοηθός προδιαγραφών μας είναι κάπως... εμπλεκόμενος ακόμα και σε αυτό το απλό παράδειγμα. Ας του ρίξουμε μια ματιά και ας το αναλύσουμε.
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
Πριν από ολόκληρη τη σουίτα, εκτελούμε την προσαρμοσμένη εντολή κατασκευής μέσω της κοκαΐνης. Αυτή η σταθερά TEST_LOGGER μπορεί να είναι λίγο υπερβολική, αλλά δεν μας απασχολεί ιδιαίτερα ο αριθμός των αντικειμένων εδώ. Εκτελούμε φυσικά τις προδιαγραφές με τυχαία σειρά και πρέπει να συμπεριλάβουμε όλα τα καλούδια από το watir-rspec. Πρέπει επίσης να ρυθμίσουμε το Billy έτσι ώστε να μην κάνει καμία προσωρινή αποθήκευση, αλλά εκτεταμένη καταγραφή στο spec/log/billy.log
. Αν δεν ξέρετε αν μια αίτηση πραγματικά παρακάμπτεται ή αν χτυπάει έναν ζωντανό διακομιστή (ουπς!), αυτό το αρχείο καταγραφής είναι χρυσός.
Είμαι βέβαιος ότι τα οξυδερκή σας μάτια έχουν ήδη εντοπίσει τα ProxySupport και BrowserSupport. Μπορεί να νομίζετε ότι τα προσαρμοσμένα καλούδια μας βρίσκονται εκεί... και θα είχατε ακριβώς δίκιο! Ας δούμε πρώτα τι κάνει το BrowserSupport.
Ένα πρόγραμμα περιήγησης, ελεγχόμενο
Κατ' αρχάς, ας παρουσιάσουμε TempBrowser
:
κλάση 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
Δουλεύοντας προς τα πίσω μέσα από το δέντρο κλήσεων, μπορούμε να δούμε ότι ρυθμίζουμε ένα σετ επιλογών προγράμματος περιήγησης Selenium για το Chrome. Μία από τις επιλογές που περνάμε σε αυτήν είναι καθοριστική για τη ρύθμιση μας: δίνει εντολή στην περίπτωση του Chrome να μεταβιβάζει τα πάντα μεσολάβησης μέσω της περίπτωσης του Puffing Billy. Η άλλη επιλογή είναι απλά καλή για να την έχουμε - κάθε περίπτωση που τρέχουμε και δεν είναι ακέφαλος θα ανοίξουν αυτόματα τα εργαλεία επιθεώρησης. Αυτό μας γλιτώνει από αναρίθμητες ποσότητες Cmd+Alt+I ανά ημέρα 😉.
Αφού ρυθμίσουμε το πρόγραμμα περιήγησης με αυτές τις επιλογές, το μεταβιβάζουμε στο Watir και αυτό είναι σχεδόν όλο. Το kill
είναι μια μικρή βοήθεια που μας επιτρέπει να σταματάμε και να επανεκκινούμε επανειλημμένα το πρόγραμμα οδήγησης αν χρειαστεί, χωρίς να πετάμε την περίπτωση TempBrowser.
Τώρα μπορούμε να δώσουμε στα παραδείγματά μας rspec μερικές υπερδυνάμεις. Πρώτα απ' όλα, παίρνουμε ένα έξυπνο πρόγραμμα περιήγησης
βοηθητική μέθοδος γύρω από την οποία θα περιστρέφονται κυρίως οι προδιαγραφές μας. Μπορούμε επίσης να επωφεληθούμε από μια εύχρηστη μέθοδο για την επανεκκίνηση του προγράμματος περιήγησης για ένα συγκεκριμένο παράδειγμα, αν κάνουμε κάτι εξαιρετικά ευαίσθητο. Φυσικά, θέλουμε επίσης να τερματίσουμε το πρόγραμμα περιήγησης μετά την ολοκλήρωση της σουίτας δοκιμών, επειδή σε καμία περίπτωση δεν θέλουμε να παραμένουν περιπτώσεις του Chrome - για χάρη της μνήμης RAM μας.
ενότητα BrowserSupport
def self.browser
@browser ||= TempBrowser.new
end
def self.configure(config)
config.around(:each) 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
Έχουμε εγκαταστήσει ένα πρόγραμμα περιήγησης και βοηθητικά προγράμματα spec και είμαστε έτοιμοι να αρχίσουμε να στέλνουμε αιτήσεις μεσολάβησης στον μεσάζοντα μεσολάβησης. Αλλά περιμένετε, δεν το έχουμε ρυθμίσει ακόμα! Θα μπορούσαμε να χτυπήσουμε επανειλημμένες κλήσεις στο Billy για κάθε παράδειγμα, αλλά είναι προτιμότερο να αποκτήσουμε μερικές βοηθητικές μεθόδους και να γλιτώσουμε μερικές χιλιάδες πληκτρολογήσεις. Αυτό είναι που ProxySupport
κάνει.
Αυτή που χρησιμοποιούμε στη δοκιμαστική μας εγκατάσταση είναι ελαφρώς πιο περίπλοκη, αλλά εδώ είναι μια γενική ιδέα:
frozenstringliteral: true
require 'json'
ενότητα 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: dup
})
τέλος
def stubstatus(url, status)
Billy.proxy.stub(url).andreturn({
body: '',
code: status,
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
Μπορούμε να βγάλουμε:
- Αιτήματα σελίδας HTML - για την κύρια σελίδα "παιδική χαρά",
- αιτήματα JS - για την εξυπηρέτηση της ομαδοποιημένης βιβλιοθήκης μας,
- Αιτήσεις JSON - για να παρακάμψετε την αίτηση προς το απομακρυσμένο API,
- και απλά μια αίτηση "οτιδήποτε", όπου μας ενδιαφέρει μόνο να επιστρέψουμε μια συγκεκριμένη, μη-200 απάντηση HTTP.
Αυτό θα είναι αρκετό για το απλό μας παράδειγμα. Μιλώντας για παραδείγματα - θα πρέπει να δημιουργήσουμε ένα ζευγάρι!
Δοκιμάζοντας την καλή πλευρά
Πρέπει πρώτα να συνδέσουμε μερικές "διαδρομές" για τον πληρεξούσιο μεσολάβησης:
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
Αξίζει να σημειωθεί ότι από την οπτική γωνία του rspec οι σχετικές διαδρομές εδώ αναφέρονται στον κύριο κατάλογο του έργου, οπότε φορτώνουμε την HTML και το JS μας κατευθείαν από το dist
κατάλογο - όπως κατασκευάστηκε από το Parcel. Μπορείτε ήδη να δείτε πώς αυτά τα stub_*
οι βοηθοί είναι χρήσιμοι.
Αξίζει επίσης να σημειωθεί ότι τοποθετούμε την "ψεύτικη" ιστοσελίδα μας σε ένα .local
TLD. Με αυτόν τον τρόπο, τυχόν ανεξέλεγκτες αιτήσεις δεν θα πρέπει να ξεφύγουν από το τοπικό μας περιβάλλον, αν κάτι πάει στραβά. Ως γενική πρακτική θα συνιστούσα τουλάχιστον να μην χρησιμοποιείτε "πραγματικά" ονόματα τομέα σε stubs εκτός αν είναι απολύτως απαραίτητο.
Μια άλλη σημείωση που πρέπει να κάνουμε εδώ είναι να μην επαναλαμβάνουμε τους εαυτούς μας. Καθώς η δρομολόγηση μεσολάβησης γίνεται πιο πολύπλοκη, με πολύ περισσότερες διαδρομές και διευθύνσεις URL, θα υπάρχει πραγματική αξία στο να εξαγάγουμε αυτή τη ρύθμιση σε ένα κοινόχρηστο πλαίσιο και να τη συμπεριλάβουμε απλά όταν χρειάζεται.
Τώρα μπορούμε να προσδιορίσουμε πώς θα πρέπει να μοιάζει η "καλή" μας διαδρομή:
context 'με σωστή απάντηση' 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 'logs proper data' do
expect(browser.execute_script('return window.logs')).to(
eq(['[ΠΑΡΑΔΕΙΓΜΑ] Remote fetch successful', '[ΠΑΡΑΔΕΙΓΜΑ] BTC to ETH: 0.03619999'])
)
end
end
Αυτό είναι πολύ απλό, έτσι δεν είναι; Λίγες ακόμα ρυθμίσεις εδώ - απομονώνουμε την απόκριση JSON από το απομακρυσμένο API με ένα προσάρτημα, πηγαίνουμε στην κύρια διεύθυνση URL μας και στη συνέχεια... περιμένουμε.
Η μεγαλύτερη αναμονή
Οι αναμονές είναι ένας τρόπος για να παρακάμψουμε έναν περιορισμό που αντιμετωπίσαμε με το Watir - δεν μπορούμε να περιμένουμε αξιόπιστα για γεγονότα π.χ. JavaScript, οπότε πρέπει να κλέψουμε λίγο και να "περιμένουμε" μέχρι τα σενάρια να μετακινήσουν κάποιο αντικείμενο στο οποίο μπορούμε να έχουμε πρόσβαση σε μια κατάσταση που μας ενδιαφέρει. Το μειονέκτημα είναι ότι αν αυτή η κατάσταση δεν έρθει ποτέ (π.χ. λόγω σφάλματος) πρέπει να περιμένουμε να λήξει ο χρόνος του watir waiter. Αυτό ανεβάζει λίγο το χρόνο του spec. Το spec εξακολουθεί να αποτυγχάνει αξιόπιστα, ωστόσο.
Αφού η σελίδα "σταθεροποιηθεί" στην κατάσταση που μας ενδιαφέρει, μπορούμε να εκτελέσουμε μερικά ακόμη JavaScript στο πλαίσιο της σελίδας. Εδώ καλούμε τα αρχεία καταγραφής που γράφτηκαν στον δημόσιο πίνακα και ελέγχουμε αν είναι αυτά που περιμέναμε.
Ως δευτερεύουσα σημείωση - εδώ είναι το σημείο όπου το stubbing του απομακρυσμένου αιτήματος πραγματικά λάμπει. Η απάντηση που καταγράφεται στην κονσόλα εξαρτάται από την ισοτιμία που επιστρέφει το απομακρυσμένο API, οπότε δεν θα μπορούσαμε να ελέγξουμε αξιόπιστα τα περιεχόμενα του αρχείου καταγραφής αν άλλαζαν συνεχώς. Υπάρχουν βέβαια τρόποι να το παρακάμψουμε, αλλά δεν είναι πολύ κομψοί.
Δοκιμή του κακού κλάδου
Ένα ακόμη πράγμα που πρέπει να ελέγξετε: το υποκατάστημα "αποτυχία".
context 'με αποτυχημένη απάντηση' 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 'αποτυχία καταγραφής' do
expect(browser.execute_script('return window.logs')).to(
eq(['[ΠΑΡΑΔΕΙΓΜΑ] Remote fetch failed'])
)
end
end
Είναι πολύ παρόμοιο με το παραπάνω, με τη διαφορά ότι η απόκριση θα επιστρέψει έναν κωδικό κατάστασης HTTP 404 και θα περιμένει ένα διαφορετικό αρχείο καταγραφής.
Ας τρέξουμε τις προδιαγραφές μας τώρα.
% δέσμη exec rspec
Τυχαιοποιημένη με σπόρο 63792
I, [2017-12-21T14:26:08.680953 #7303] INFO -- : Εντολή :: npm run build
Απομακρυσμένη κλήση
με σωστή απάντηση
καταγράφει σωστά δεδομένα
με αποτυχημένη απόκριση
καταγράφει αποτυχία
Ολοκληρώθηκε σε 23,56 δευτερόλεπτα (τα αρχεία χρειάστηκαν 0,86547 δευτερόλεπτα για να φορτωθούν)
2 παραδείγματα, 0 αποτυχίες
Γιούχου!
Συμπέρασμα
Συζητήσαμε εν συντομία τον τρόπο με τον οποίο το JavaScript μπορεί να δοκιμαστεί με τη Ruby. Ενώ αρχικά θεωρούσαμε ότι ήταν περισσότερο μια προσωρινή λύση, είμαστε αρκετά ικανοποιημένοι με το μικρό μας πρωτότυπο τώρα. Φυσικά, εξακολουθούμε να εξετάζουμε το ενδεχόμενο μιας καθαρής λύσης JavaScript, αλλά στο μεταξύ έχουμε έναν απλό και πρακτικό τρόπο αναπαραγωγής και δοκιμής κάποιων πολύ σύνθετων καταστάσεων που έχουμε συναντήσει στη φύση.
Αν σκέφτεστε να κατασκευάσετε κάτι παρόμοιο μόνοι σας, θα πρέπει να σημειωθεί ότι δεν είναι χωρίς περιορισμούς. Για παράδειγμα, αν αυτό που δοκιμάζετε είναι πραγματικά βαρύ με AJAX, το Puffing Billy θα αργήσει πολύ να ανταποκριθεί. Επίσης, αν πρέπει να κόψετε κάποιες πηγές SSL, θα χρειαστεί να κάνετε περισσότερες παρεμβάσεις - κοιτάξτε την τεκμηρίωση του watir, αν είναι μια απαίτηση που έχετε. Σίγουρα θα συνεχίσουμε να εξερευνούμε και να αναζητούμε τους καλύτερους τρόπους για να αντιμετωπίσουμε τη μοναδική μας περίπτωση χρήσης - και θα φροντίσουμε να σας ενημερώσουμε και για το τι ανακαλύψαμε.