() translation by (you can also view the original English article)
Cepat atau lambat, semua pengembang harus berinteraksi dengan API. Bagian yang paling sulit adalah selalu terkait dengan pengujian andal kode yang ditulis, dan, karena ingin memastikan semuanya bekerja dengan baik, kita terus menjalankan kode yang menanyakan API itu sendiri. Proses ini lambat dan tidak efisien, karena didapat mengalami masalah jaringan dan inkonsistensi data (hasil API dapat berubah). Mari kita tinjau bagaimana kita dapat menghindari semua upaya ini dengan Ruby.
Tujuan kita
"Aliran itu penting: tulis tes, jalankan, dan lihat gagal, lalu tulis kode implementasi minimal untuk membuatnya lulus. Setelah semuanya selesai, lakukan refactor jika diperlukan."
Tujuan kita sederhana: menulis pembungkus kecil di sekitar API Dribbble untuk mengambil informasi tentang pengguna (disebut 'pemain' di dunia Dribbble).
Karena kita akan menggunakan Ruby, kita juga akan mengikuti pendekatan TDD: jika Anda tidak terbiasa dengan teknik ini, Nettuts+ memiliki primer yang bagus di RSpec yang dapat Anda baca. Singkatnya, kita akan menulis tes sebelum menulis implementasi kode, membuatnya lebih mudah untuk menemukan bug dan untuk mencapai kualitas kode yang tinggi. Flow sangat penting: tulis tes, jalankan dan lihat gagal, lalu tulis kode implementasi minimal untuk membuatnya lulus. Setelah semuanya selesai, lakukan refactor jika diperlukan.
API
API Dribbble cukup mudah. Pada saat ini hanya mendukung permintaan GET dan tidak memerlukan otentikasi: kandidat yang ideal untuk tutorial. Selain itu, ia menawarkan batas 60 panggilan per menit, batasan yang secara sempurna menunjukkan mengapa bekerja dengan API memerlukan pendekatan yang cerdas.
Konsep Kunci
Tutorial ini perlu mengasumsikan bahwa Anda memiliki keakraban dengan konsep pengujian: perlengkapan, cemoohan, harapan. Pengujian adalah topik penting (terutama di komunitas Ruby) dan bahkan jika Anda bukan seorang Rubyist, saya mendorong Anda untuk menggali lebih dalam masalah ini dan untuk mencari alat yang setara untuk bahasa sehari-hari Anda. Anda mungkin ingin membaca "Buku RSpec" oleh David Chelimsky et al., Primer yang sangat baik tentang Pengembangan Behaviour Driven Development.
Untuk meringkas di sini, berikut adalah tiga konsep utama yang harus Anda ketahui:
- Mock: disebut juga dobel, mock adalah "objek yang berdiri untuk objek lain dalam contoh". Ini berarti bahwa jika kita ingin menguji interaksi antara suatu objek dan lainnya, kita dapat mengejek yang kedua. Dalam tutorial ini, kita akan mengejek API Dribbble, untuk menguji kode kita, kita tidak perlu API itu sendiri, tetapi sesuatu yang berperilaku seperti itu dan memperlihatkan interface yang sama.
- Fixture: dataset yang menciptakan kembali keadaan tertentu dalam sistem. Fixture dapat digunakan untuk membuat data yang diperlukan untuk menguji sepotong logika.
- Expectation: contoh uji yang ditulis dari sudut pandang hasil yang ingin kita capai.
Alat Kami
"Sebagai praktik umum, jalankan tes setiap kali Anda memperbaruinya."
WebMock adalah pustaka tiruan Ruby yang digunakan untuk mengolok-olok (atau mematikan) permintaan http. Dengan kata lain, ini memungkinkan Anda untuk mensimulasikan permintaan HTTP tanpa benar-benar membuat permintaan. Keuntungan utama untuk ini adalah mampu mengembangkan dan menguji terhadap layanan HTTP apa pun tanpa memerlukan layanan itu sendiri dan tanpa menimbulkan masalah terkait (seperti batas API, batasan IP, dan semacamnya).
VCR adalah complementary tool yang mencatat setiap permintaan http nyata dan membuat fixture, file yang berisi semua data yang diperlukan untuk mereplikasi permintaan itu tanpa melakukannya lagi. Kita akan mengonfigurasinya untuk menggunakan WebMock untuk melakukan itu. Dengan kata lain, pengujian kita akan berinteraksi dengan Dribbble API yang sebenarnya hanya sekali: setelah itu, WebMock akan mematikan semua permintaan berkat data yang direkam oleh VCR. Kita akan memiliki replika respons Dribbble API yang sempurna yang direkam secara lokal. Selain itu, WebMock akan memungkinkan kita menguji kasus tepi (seperti waktu permintaan habis) dengan mudah dan konsisten. Konsekuensi luar biasa dari pengaturan kita adalah semuanya akan sangat cepat.
Sedangkan untuk pengujian unit, kita akan menggunakan Minitest. Ini adalah perpustakaan pengujian unit yang cepat dan sederhana yang juga mendukung ekspektasi dalam mode RSpec. Ini menawarkan serangkaian fitur yang lebih kecil, tetapi saya menemukan bahwa ini sebenarnya mendorong dan mendorong Anda untuk memisahkan logika Anda menjadi metode yang kecil dan dapat diuji. Minitest adalah bagian dari Ruby 1.9, jadi jika Anda menggunakannya (saya harap begitu) Anda tidak perlu menginstalnya. Di Ruby 1.8, itu hanya masalah gem install minitest
.
Saya akan menggunakan Ruby 1.9.3: jika tidak, Anda mungkin akan menemukan beberapa masalah yang terkait dengan require_relative
, tetapi saya telah memasukkan kode cadangan dalam komentar tepat di bawahnya. Sebagai praktik umum, Anda harus menjalankan tes setiap kali Anda memperbaruinya, bahkan jika saya tidak akan menyebutkan langkah ini secara eksplisit di seluruh tutorial.
Setup



Kita akan menggunakan struktur folder /lib
dan /spec
konvensional untuk mengatur kode. Adapun nama perpustakaan, kita akan menyebutnya Dish, mengikuti konvensi Dribbble tentang penggunaan istilah terkait bola basket.
Gemfile akan berisi semua dependensi kita, meskipun cukup kecil.
1 |
source :rubygems |
2 |
|
3 |
gem 'httparty' |
4 |
|
5 |
group :test do |
6 |
gem 'webmock' |
7 |
gem 'vcr' |
8 |
gem 'turn' |
9 |
gem 'rake' |
10 |
end
|
Httparty adalah permata yang mudah digunakan untuk menangani permintaan HTTP; itu akan menjadi inti dari perpustakaan kita. Dalam kelompok uji, kita juga akan menambahkan Turn untuk mengubah output pengujian kita menjadi lebih deskriptif dan untuk mendukung warna.
Folder /lib
dan /spec
memiliki struktur simetris: untuk setiap file yang terkandung dalam folder /lib/dish
, harus ada file di dalam /spec/dish
dengan nama yang sama dan akhiran ‘_spec’.
Mari kita mulai dengan membuat file /lib/dish.rb
dan tambahkan kode berikut:
1 |
require "httparty" |
2 |
Dir[File.dirname(__FILE__) + '/dish/*.rb'].each do |file| |
3 |
require file |
4 |
end
|
Itu tidak melakukan banyak hal: itu membutuhkan 'httparty' dan kemudian mengulangi setiap file .rb
di dalam /lib/dish
untuk memerlukannya. Dengan file ini di tempat, kita akan dapat menambahkan fungsionalitas apa pun di dalam file terpisah di /lib/dish
dan membuatnya secara otomatis dimuat hanya dengan memerlukan file tunggal ini.
Mari kita pindah ke folder /spec
. Inilah konten file spec_helper.rb
.
1 |
#we need the actual library file
|
2 |
require_relative '../lib/dish' |
3 |
# For Ruby < 1.9.3, use this instead of require_relative
|
4 |
# require(File.expand_path('../../lib/dish', __FILE__))
|
5 |
|
6 |
#dependencies
|
7 |
require 'minitest/autorun' |
8 |
require 'webmock/minitest' |
9 |
require 'vcr' |
10 |
require 'turn' |
11 |
|
12 |
Turn.config do |c| |
13 |
# :outline - turn's original case/test outline mode [default]
|
14 |
c.format = :outline |
15 |
# turn on invoke/execute tracing, enable full backtrace
|
16 |
c.trace = true |
17 |
# use humanized test names (works only with :outline format)
|
18 |
c.natural = true |
19 |
end
|
20 |
|
21 |
#VCR config
|
22 |
VCR.config do |c| |
23 |
c.cassette_library_dir = 'spec/fixtures/dish_cassettes' |
24 |
c.stub_with :webmock |
25 |
end
|
Ada beberapa hal yang perlu diperhatikan di sini, jadi mari kita hancurkan sepotong demi sepotong:
- Pada awalnya, kita memerlukan file lib utama untuk aplikasi, membuat kode yang ingin kita uji tersedia untuk test suite. Pernyataan
require_relative
adalah tambahan Ruby 1.9.3. - Kita kemudian membutuhkan semua dependensi pustaka:
minitest/autorun
mencakup semua harapan yang akan digunakan,webmock/minitest
menambahkan binding yang diperlukan antara kedua pustaka, sementaravcr
danturn
cukup jelas. - Blok Turn config hanya perlu mengubah hasil pengujian. Kita akan menggunakan format outline, di mana kita dapat melihat deskripsi spesifikasi.
- Blok konfigurasi VCR memberi tahu VCR untuk menyimpan permintaan ke folder fixture (perhatikan jalur relatif) dan untuk menggunakan WebMock sebagai pustaka stubbing (VCR mendukung beberapa lainnya).
Terakhir, namun tidak kalah pentingnya, Rakefile
yang berisi beberapa kode dukungan:
1 |
require 'rake/testtask' |
2 |
|
3 |
Rake::TestTask.new do |t| |
4 |
t.test_files = FileList['spec/lib/dish/*_spec.rb'] |
5 |
t.verbose = true |
6 |
end
|
7 |
|
8 |
task :default => :test |
Perpustakaan rake/testtask
termasuk kelas TestTask
yang berguna untuk mengatur lokasi file pengujian. Mulai sekarang, untuk menjalankan spesifikasi, kita hanya akan mengetik rake
dari direktori root perpustakaan.
Sebagai cara untuk menguji konfigurasi kita, mari tambahkan kode berikut ke /lib/dish/player.rb
:
1 |
module Dish |
2 |
class Player |
3 |
end
|
4 |
end
|
Kemudian /spec/lib/dish/player_spec.rb
:
1 |
require_relative '../../spec_helper' |
2 |
# For Ruby < 1.9.3, use this instead of require_relative
|
3 |
# require (File.expand_path('./../../../spec_helper', __FILE__))
|
4 |
|
5 |
describe Dish::Player do |
6 |
|
7 |
it "must work" do |
8 |
"Yay!".must_be_instance_of String |
9 |
end
|
10 |
|
11 |
end
|
Menjalankan rake
seharusnya memberi Anda satu tes yang lulus dan tidak ada kesalahan. Tes ini sama sekali tidak berguna untuk proyek kita, namun secara implisit memverifikasi bahwa struktur file perpustakaan kita sudah ada (blok describe
akan menimbulkan kesalahan jika modul Dish::Player
tidak dimuat).
Spesifikasi Pertama
Agar berfungsi dengan benar, Dish membutuhkan modul Httparty dan base_uri
yang benar, mis. Url dasar dari Dribbble API. Mari kita tulis tes yang relevan untuk persyaratan ini di player_spec.rb
:
1 |
...
|
2 |
describe Dish::Player do |
3 |
|
4 |
describe "default attributes" do |
5 |
|
6 |
it "must include httparty methods" do |
7 |
Dish::Player.must_include HTTParty |
8 |
end
|
9 |
|
10 |
it "must have the base url set to the Dribble API endpoint" do |
11 |
Dish::Player.base_uri.must_equal 'http://api.dribbble.com' |
12 |
end
|
13 |
|
14 |
end
|
15 |
|
16 |
end
|
Seperti yang Anda lihat, harapan Minitest cukup jelas, terutama jika Anda adalah pengguna RSpec: perbedaan terbesar adalah kata-kata, di mana Minitest lebih suka "must/wont" daripada "should/should_not".
Menjalankan tes ini akan menunjukkan satu kesalahan dan satu kegagalan. Agar mereka lulus, mari tambahkan baris pertama kita kode implementasi ke player.rb
:
1 |
module Dish |
2 |
|
3 |
class Player |
4 |
|
5 |
include HTTParty |
6 |
|
7 |
base_uri 'http://api.dribbble.com' |
8 |
|
9 |
end
|
10 |
|
11 |
end
|
Running rake
lagi harus menunjukkan dua spesifikasi yang lewat. Sekarang kelas Player
kita memiliki akses ke semua metode kelas Httparty, seperti get
atau post
.
Merekam Permintaan Pertama kita
Karena kita akan bekerja pada kelas Player
, kita perlu memiliki data API untuk pemain. Halaman dokumentasi Dribbble API menunjukkan bahwa titik akhir untuk mendapatkan data tentang pemain tertentu adalah http://api.dribbble.com/players/:id
Seperti dalam mode Rails yang khas, :id
adalah id atau username tertentu. Kita akan menggunakan simplebits
, nama pengguna Dan Cederholm, salah satu pendiri Dribbble.
Untuk merekam permintaan dengan VCR, mari perbarui file player_spec.rb
kita dengan menambahkan blok describe
berikut ke spec, tepat setelah yang pertama:
1 |
...
|
2 |
|
3 |
describe "GET profile" do |
4 |
|
5 |
before do |
6 |
VCR.insert_cassette 'player', :record => :new_episodes |
7 |
end
|
8 |
|
9 |
after do |
10 |
VCR.eject_cassette |
11 |
end
|
12 |
|
13 |
it "records the fixture" do |
14 |
Dish::Player.get('/players/simplebits') |
15 |
end
|
16 |
|
17 |
end
|
18 |
|
19 |
end
|
Setelah menjalankan
rake
, Anda dapat memverifikasi bahwa fixture telah dibuat. Mulai sekarang, semua pengujian kita akan sepenuhnya independen dari jaringan.
Blok before
digunakan untuk mengeksekusi bagian kode tertentu sebelum setiap harapan: kita menggunakannya untuk menambahkan makro VCR yang digunakan untuk merekam fixture yang akan kita sebut 'pemain'. Ini akan membuat file player.yml
di bawah spec/fixtures/dish_cassettes
. Opsi :record
diset untuk merekam semua permintaan baru satu kali dan mengulanginya pada setiap permintaan berikutnya yang identik. Sebagai bukti konsep, kita dapat menambahkan spec yang hanya bertujuan untuk merekam fixture untuk profil simplebits. Arahan after
memberi tahu VCR untuk melepaskan kaset setelah pengujian, memastikan bahwa semuanya terisolasi dengan benar. Metode get
pada kelas Player
tersedia, berkat masuknya modul Httparty
.
Setelah menjalankan rake
, Anda dapat memverifikasi bahwa fixture telah dibuat. Mulai sekarang, semua pengujian kami akan sepenuhnya independen dari jaringan.
Mendapatkan Profil Pemain



Setiap pengguna Dribbble memiliki profil yang berisi jumlah data yang cukup luas. Mari kita pikirkan bagaimana kita ingin perpustakaan menjadi ketika benar-benar digunakan: ini adalah cara yang berguna untuk menyempurnakan DSL kita akan bekerja. Inilah yang ingin dicapai:
1 |
simplebits = Dish::Player.new('simplebits') |
2 |
simplebits.profile |
3 |
=> #returns a hash with all the data from the API |
4 |
simplebits.username |
5 |
=> 'simplebits' |
6 |
simplebits.id |
7 |
=> 1 |
8 |
simplebits.shots_count |
9 |
=> 157 |
Sederhana dan efektif: kita ingin membuat Instanate Player dengan menggunakan nama pengguna dan kemudian mendapatkan akses ke datanya dengan memanggil metode pada contoh yang memetakan ke atribut yang dikembalikan oleh API. Kita harus konsisten dengan API itu sendiri.
Mari kita selesaikan satu per satu dan menulis beberapa tes terkait untuk mendapatkan data pemain dari API. Kita dapat memodifikasi blok "GET profile"
kita agar:
1 |
describe "GET profile" do |
2 |
|
3 |
let(:player) { Dish::Player.new } |
4 |
|
5 |
before do |
6 |
VCR.insert_cassette 'player', :record => :new_episodes |
7 |
end
|
8 |
|
9 |
after do |
10 |
VCR.eject_cassette |
11 |
end
|
12 |
|
13 |
it "must have a profile method" do |
14 |
player.must_respond_to :profile |
15 |
end
|
16 |
|
17 |
it "must parse the api response from JSON to Hash" do |
18 |
player.profile.must_be_instance_of Hash |
19 |
end
|
20 |
|
21 |
it "must perform the request and get the data" do |
22 |
player.profile["username"].must_equal 'simplebits' |
23 |
end
|
24 |
|
25 |
end
|
Arahan let
di atas membuat instance Dish::Player
yang tersedia di harapan. Selanjutnya, kita ingin memastikan bahwa pemain kita telah mendapatkan metode profil yang nilainya berupa hash yang mewakili data dari API. Sebagai langkah terakhir, kita menguji kunci sampel (nama pengguna) untuk memastikan bahwa kita benar-benar melakukan permintaan.
Perhatikan bahwa kita belum menangani cara mengatur nama pengguna, karena ini merupakan langkah lebih lanjut. Implementasi minimal yang diperlukan adalah sebagai berikut:
1 |
...
|
2 |
class Player |
3 |
|
4 |
include HTTParty |
5 |
|
6 |
base_uri 'http://api.dribbble.com' |
7 |
|
8 |
def profile |
9 |
self.class.get '/players/simplebits' |
10 |
end
|
11 |
|
12 |
end
|
13 |
...
|
Jumlah kode yang sangat sedikit: kita baru saja membungkus panggilan masuk dalam metode profile
. Kita kemudian melewati jalur hardcode untuk mengambil data penyederhanaan, data yang telah simpan berkat VCR.
Semua tes kita harus lulus.
Pengaturan Nama Pengguna
Sekarang kita memiliki fungsi profil yang berfungsi, kita dapat menangani nama pengguna. Berikut spesifikasi yang relevan:
1 |
describe "default instance attributes" do |
2 |
|
3 |
let(:player) { Dish::Player.new('simplebits') } |
4 |
|
5 |
it "must have an id attribute" do |
6 |
player.must_respond_to :username |
7 |
end
|
8 |
|
9 |
it "must have the right id" do |
10 |
player.username.must_equal 'simplebits' |
11 |
end
|
12 |
|
13 |
end
|
14 |
|
15 |
describe "GET profile" do |
16 |
|
17 |
let(:player) { Dish::Player.new('simplebits') } |
18 |
|
19 |
before do |
20 |
VCR.insert_cassette 'base', :record => :new_episodes |
21 |
end
|
22 |
|
23 |
after do |
24 |
VCR.eject_cassette |
25 |
end
|
26 |
|
27 |
it "must have a profile method" do |
28 |
player.must_respond_to :profile |
29 |
end
|
30 |
|
31 |
it "must parse the api response from JSON to Hash" do |
32 |
player.profile.must_be_instance_of Hash |
33 |
end
|
34 |
|
35 |
it "must get the right profile" do |
36 |
player.profile["username"].must_equal "simplebits" |
37 |
end
|
38 |
|
39 |
end
|
Kita telah menambahkan blok uraian baru untuk memeriksa nama pengguna yang akan ditambahkan dan cukup mengubah inisialisasi player
di blok GET profile
untuk mencerminkan DSL yang ingin dimiliki. Menjalankan spesifikasi sekarang akan mengungkapkan banyak kesalahan, karena kelas Player
kita tidak menerima argumen saat diinisialisasi (untuk saat ini).
Implementasinya sangat mudah:
1 |
...
|
2 |
class Player |
3 |
|
4 |
attr_accessor :username |
5 |
|
6 |
include HTTParty |
7 |
|
8 |
base_uri 'http://api.dribbble.com' |
9 |
|
10 |
def initialize(username) |
11 |
self.username = username |
12 |
end
|
13 |
|
14 |
def profile |
15 |
self.class.get "/players/#{self.username}" |
16 |
end
|
17 |
|
18 |
end
|
19 |
...
|
Metode inisialisasi mendapatkan nama pengguna yang disimpan di dalam kelas berkat metode attr_accessor
yang ditambahkan di atas. Kita kemudian mengubah metode profil untuk menginterpolasi atribut nama pengguna.
Kita harus lulus semua tes sekali lagi.
Atribut Dinamis
Pada tingkat dasar, lib kita dalam kondisi yang cukup baik. Karena profil adalah Hash, kita bisa berhenti di sini dan sudah menggunakannya dengan melewati kunci atribut yang ingin kita dapatkan nilainya. Tujuan kami, bagaimanapun, adalah untuk membuat DSL yang mudah digunakan yang memiliki metode untuk setiap atribut.
Mari kita pikirkan tentang apa yang perlu kita capai. Mari kita asumsikan kita memiliki instance pemain dan matikan cara kerjanya:
1 |
player.username |
2 |
=> 'simplebits' |
3 |
player.shots_count |
4 |
=> 157 |
5 |
player.foo_attribute |
6 |
=> NoMethodError |
Mari terjemahkan ini ke dalam spesifikasi dan tambahkan ke blok GET profile
:
1 |
...
|
2 |
describe "dynamic attributes" do |
3 |
|
4 |
before do |
5 |
player.profile |
6 |
end
|
7 |
|
8 |
it "must return the attribute value if present in profile" do |
9 |
player.id.must_equal 1 |
10 |
end
|
11 |
|
12 |
it "must raise method missing if attribute is not present" do |
13 |
lambda { player.foo_attribute }.must_raise NoMethodError |
14 |
end
|
15 |
|
16 |
end
|
17 |
...
|
Kita sudah memiliki spesifikasi untuk nama pengguna, jadi kita tidak perlu menambahkan yang lain. Perhatikan beberapa hal:
- Kita secara eksplisit memanggil
player.profile
di blok sebelum, jika tidak maka akan menjadi nol ketika kita mencoba untuk mendapatkan nilai atribut. - untuk menguji bahwa
foo_attribute
memunculkan eksepsi, kita perlu membungkusnya dalam lambda dan memeriksa apakah itu memunculkan error yang diharapkan. - kami menguji
id
sama dengan1
, seperti yang kita tahu bahwa itu adalah nilai yang diharapkan (ini murni tes yang bergantung pada data).
Dari segi implementasi, kita dapat mendefinisikan serangkaian metode untuk mengakses hash profile
, namun ini akan membuat banyak duplikasi logika. Selain itu, akan bergantung pada hasil API untuk selalu memiliki kunci yang sama.
"Kita akan mengandalkan
metode_missing
untuk menangani kasus ini dan 'menghasilkan' semua metode tersebut dengan cepat."
Sebagai gantinya, kita akan mengandalkan metode_missing
untuk menangani kasus ini dan 'menghasilkan' semua metode tersebut dengan cepat. Tapi apa artinya ini? Tanpa terlalu banyak metaprogramming, kita bisa mengatakan bahwa setiap kali kita memanggil metode yang tidak ada pada objek, Ruby memunculkan NoMethodError
dengan menggunakan metode_missing
. Dengan mendefinisikan kembali metode ini di dalam kelas, kita dapat memodifikasi perilakunya.
Dalam kasus, kita akan mencegat panggilan method_missing
, memverifikasi bahwa nama metode yang telah dipanggil adalah kunci dalam hash profil dan dalam hal hasil positif, kembalikan nilai hash untuk kunci itu. Jika tidak, kita akan memanggil super
untuk meningkatkan standar NoMethodError
: ini diperlukan untuk memastikan bahwa perpustakaan berperilaku tepat seperti yang dilakukan perpustakaan lain. Dengan kata lain, kita ingin menjamin kejutan seminimal mungkin.
Mari kita tambahkan kode berikut ke kelas Player
:
1 |
def method_missing(name, *args, &block) |
2 |
if profile.has_key?(name.to_s) |
3 |
profile[name.to_s] |
4 |
else
|
5 |
super
|
6 |
end
|
7 |
end
|
Kode melakukan apa yang dijelaskan di atas. Jika sekarang Anda menjalankan spesifikasi, Anda harus memiliki semuanya. Saya akan memasukkan Anda untuk menambahkan lagi ke file spesifikasi untuk beberapa atribut lainnya, seperti shots_count
.
Implementasi ini, bagaimanapun, bukan Ruby yang benar-benar idiomatis. Ini berfungsi, tetapi dapat dirampingkan menjadi operator ternary, suatu bentuk kondensasi dari kondisional if-else. Itu dapat ditulis ulang sebagai:
1 |
def method_missing(name, *args, &block) |
2 |
profile.has_key?(name.to_s) ? profile[name.to_s] : super |
3 |
end
|
Ini tidak hanya masalah panjang, tetapi juga masalah konsistensi dan konvensi bersama antara pengembang. Menjelajahi kode sumber dari permata dan perpustakaan Ruby adalah cara yang baik untuk membiasakan diri dengan konvensi ini.
Caching
Sebagai langkah terakhir, kita ingin memastikan bahwa perpustakaan efisien. Seharusnya tidak membuat permintaan lebih dari yang dibutuhkan dan mungkin menyimpan data secara internal. Sekali lagi, mari kita pikirkan bagaimana kita dapat menggunakannya:
1 |
player.profile |
2 |
=> performs the request and returns a Hash |
3 |
player.profile |
4 |
=> returns the same hash |
5 |
player.profile(true) |
6 |
=> forces the reload of the http request and then returns the hash (with data changes if necessary) |
Bagaimana kita bisa menguji ini? Kita dapat menggunakan WebMock untuk mengaktifkan dan menonaktifkan koneksi jaringan ke titik akhir API. Bahkan jika kita menggunakan perlengkapan VCR, WebMock dapat mensimulasikan Timeout jaringan atau respons berbeda terhadap server. Dalam kasus ini, kita dapat menguji caching dengan mendapatkan profil sekali dan kemudian menonaktifkan jaringan. Dengan memanggil player.profile
lagi kita akan melihat data yang sama, sementara dengan memanggil player.profile(true)
kita harus mendapatkan Timeout::Error
, karena perpustakaan akan mencoba untuk terhubung ke titik akhir API (dinonaktifkan) API.
Mari kita tambahkan blok lain ke file player_spec.rb
, tepat setelah dynamic attribute generation
:
1 |
describe "caching" do |
2 |
|
3 |
# we use Webmock to disable the network connection after
|
4 |
# fetching the profile
|
5 |
before do |
6 |
player.profile |
7 |
stub_request(:any, /api.dribbble.com/).to_timeout |
8 |
end
|
9 |
|
10 |
it "must cache the profile" do |
11 |
player.profile.must_be_instance_of Hash |
12 |
end
|
13 |
|
14 |
it "must refresh the profile if forced" do |
15 |
lambda { player.profile(true) }.must_raise Timeout::Error |
16 |
end
|
17 |
|
18 |
end
|
Metode stub_request
memotong semua panggilan ke titik akhir API dan mensimulasikan batas waktu, meningkatkan batas waktu yang Timeout::Error
. Seperti yang dilakukan sebelumnya, kita menguji keberadaan kesalahan ini dalam lambda.
Implementasinya bisa rumit, jadi kita akan membaginya menjadi dua langkah. Pertama, mari pindahkan permintaan http yang sebenarnya ke metode pribadi:
1 |
...
|
2 |
def profile |
3 |
get_profile
|
4 |
end
|
5 |
|
6 |
...
|
7 |
|
8 |
private
|
9 |
|
10 |
def get_profile |
11 |
self.class.get("/players/#{self.username}") |
12 |
end
|
13 |
...
|
Ini tidak akan membuat spesifikasi kami lolos, karena kita tidak melakukan cache hasil get_profile
. Untuk melakukan itu, mari kita ubah metode profile
:
1 |
...
|
2 |
def profile |
3 |
@profile ||= get_profile |
4 |
end
|
5 |
...
|
Kita akan menyimpan hash hasil ke dalam variabel instan. Perhatikan juga operator ||=
, yang kehadirannya memastikan bahwa get_profile
dijalankan hanya jika @profile mengembalikan nilai palsu (seperti nil
).
Selanjutnya kita bisa menambahkan direktif pemuatan paksa:
1 |
...
|
2 |
def profile(force = false) |
3 |
force ? @profile = get_profile : @profile ||= get_profile |
4 |
end
|
5 |
...
|
Kita menggunakan terner lagi: jika force
salah, kita melakukan get_profile
dan cache, jika tidak, menggunakan logika yang ditulis dalam versi sebelumnya dari metode ini (yaitu melakukan permintaan hanya jika kita belum memiliki hash ).
Spesifikasi kita harus berwarna hijau sekarang dan ini juga merupakan akhir dari tutorial.
Perangkuman
Tujuan kita dalam tutorial ini adalah menulis perpustakaan kecil dan efisien untuk berinteraksi dengan Dribbble API; kita telah meletakkan dasar untuk ini terjadi. Sebagian besar logika yang ditulis dapat diabstraksikan dan digunakan kembali untuk mengakses semua titik akhir lainnya. Minitest, WebMock dan VCR telah terbukti menjadi alat yang berharga untuk membantu kita membentuk kode.
Namun, kita perlu mewaspadai peringatan kecil: VCR bisa menjadi pedang bermata dua, karena pengujian kita bisa menjadi terlalu bergantung pada data. Jika, karena alasan apa pun, API yang kami bangun melawan perubahan tanpa tanda yang terlihat (seperti nomor versi), kita dapat mengambil risiko pengujian kita bekerja dengan sempurna dengan set data, yang tidak lagi relevan. Jika demikian, menghapus dan membuat ulang fixture adalah cara terbaik untuk memastikan bahwa kode kita masih berfungsi seperti yang diharapkan.