Advertisement
  1. Code
  2. Ruby

Menulis Wrapper API di Ruby dengan TDD

Scroll to top
Read Time: 17 min

() 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

SetupSetupSetup

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, sementara vcr dan turn 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

DribbbleDribbbleDribbble

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 dengan 1, 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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.