Saat ini saya terlibat dalam implementasi interaksi dengan penyedia layanan KYC. Seperti biasa, tidak ada yang kosmik. Anda hanya perlu memilih satu set salinan dari berbagai catatan dari basis data Anda, mengunggahnya ke penyedia layanan dan meminta penyedia rekaman untuk memeriksanya.
Tahap awal pemrosesan berisi selusin operasi yang identik dengan mengirim permintaan untuk mengambil data dari pengguna tertentu dari berbagai tabel database. Ada asumsi bahwa dalam kasus ini sebagian besar kode dapat digunakan kembali sebagai abstraksi Request
. Saya akan mencoba menyarankan bagaimana ini bisa digunakan. Saya akan menulis tes pertama:
describe('Request', () => { const Request = require('./Request'); it('execute should return promise', () => { const request = new Request(); request.execute().then((result) => { expect(result).toBeNull(); }); }); });
Terlihat bagus? Mungkin tidak sempurna, tetapi pada pandangan pertama sepertinya Request
pada dasarnya adalah
yang mengembalikan Promise
dengan hasilnya? Dari sini, sangat mungkin untuk memulai. Saya akan membuat sketsa kode sehingga tes dapat dijalankan.
class Request { constructor(){} execute(){ return new Promise((resolve, reject) => { resolve(null); }); } } module.exports = Request;
Saya menjalankan npm test
dan mengamati titik hijau dari tes yang selesai di konsol.
Jadi Saya punya permintaan, dan itu bisa dieksekusi. Pada kenyataannya, bagaimanapun, saya akan perlu untuk entah bagaimana menginformasikan permintaan saya tentang di meja mana dia harus mencari data yang diperlukan dan kriteria apa yang harus dipenuhi oleh data ini. Saya akan mencoba menulis tes baru:
it('should configure request', () => { const options = { tableName: 'users', query: { id: 1 } }; request.configure(options); expect(request.options).toEqual(options); });
Ok Menurut saya, cukup. Karena saya sudah memiliki dua tes yang menggunakan turunan dari variabel request
, saya akan menginisialisasi variabel ini dalam metode khusus yang berjalan sebelum setiap tes berjalan. Dengan demikian, dalam setiap pengujian, saya akan memiliki contoh baru dari objek permintaan:
let request = null; beforeEach(() => { request = new Request(); });
Saya menerapkan fungsi ini di kelas permintaan, menambahkan metode untuk itu yang menyimpan pengaturan dalam variabel instance kelas, seperti yang ditunjukkan oleh tes.
configure(options){ this.options = options; }
Saya menjalankan tes dan sekarang saya melihat dua titik hijau. Dua dari tes saya berhasil diselesaikan. Namun demikian. Diasumsikan, bagaimanapun, bahwa pertanyaan saya akan ditujukan ke database. Sekarang mungkin layak untuk mencoba melihat sisi mana permintaan akan menerima informasi tentang basis data. Saya akan kembali ke tes dan menulis beberapa kode:
const DbMock = require('./DbMock'); let db = null; beforeEach(() => { db = new DbMock(); request = new Request(db); });
Sepertinya saya bahwa versi klasik inisialisasi melalui konstruktor sepenuhnya memenuhi persyaratan saya saat ini.
Secara alami, saya tidak akan menggunakan unit test untuk menggunakan antarmuka ke database MySQL nyata yang digunakan proyek kami. Mengapa Karena:
- Jika, alih-alih saya, salah satu kolega saya perlu mengerjakan bagian proyek ini dan melakukan tes unit, maka sebelum mereka dapat melakukan apa pun, mereka harus menghabiskan waktu dan upaya untuk menginstal dan mengatur instance mereka sendiri dari server MySQL.
- Keberhasilan unit test akan tergantung pada kebenaran pengisian data awal yang digunakan oleh database server MySQL.
- Waktu untuk menjalankan tes menggunakan database MySQL akan jauh lebih lama.
Baiklah Dan mengapa, misalnya, tidak menggunakan basis data dalam memori dalam pengujian unit? Ini akan bekerja dengan cepat, dan proses konfigurasi dan inisialisasi dapat otomatis. Itu semua benar, tetapi saat ini saya tidak melihat keuntungan dari menggunakan alat tambahan ini. Tampaknya bagi saya bahwa kebutuhan saya saat ini lebih cepat dan lebih murah (tidak perlu menghabiskan waktu belajar) menggunakan kelas dan metode
dan
-objects, yang hanya akan mensimulasikan perilaku antarmuka yang seharusnya digunakan dalam kondisi pertempuran.
Ngomong-ngomong. Dalam pertarungan, saya sarankan menggunakan rak buku bersama dengan knex . Mengapa Karena mengikuti dokumentasi tentang penginstalan, konfigurasi, dan menggunakan dua alat ini, saya berhasil membuat dan menjalankan kueri ke database dalam beberapa menit.
Apa yang mengikuti dari ini? Dari sini saya harus memodifikasi kode kelas Request
sehingga eksekusi permintaan cocok dengan antarmuka yang diekspor oleh alat tempur saya. Jadi sekarang kodenya akan terlihat seperti ini:
class Request { constructor(db){ this.db = db; } configure(options){ this.options = options; } execute(){ const table = this.db.Model.extend({ tableName: this.options.tableName }); return table.where(this.options.query).fetch(); } } module.exports = Request;
Saya akan menjalankan tes dan melihat apa yang terjadi. Ya DbMock
saja, saya tidak memiliki modul DbMock
, jadi hal pertama yang saya lakukan adalah mengimplementasikan rintisan untuk itu:
class DbMock { constructor(){} } module.exports = DbMock;
Saya akan menjalankan tes lagi. Apa sekarang? Princess Jasmine
memberi tahu saya bahwa DbMock
saya tidak mengimplementasikan properti Model
. Saya akan mencoba membuat sesuatu:
class DbMock { constructor(){ this.Model = { extend: () => {} }; } } module.exports = DbMock;
Menjalankan tes lagi. Sekarang kesalahannya adalah bahwa dalam pengujian unit saya, saya menjalankan kueri tanpa terlebih dahulu mengatur parameternya menggunakan metode configure
. Saya memperbaiki ini:
const options = { tableName: 'users', query: { id: 1 } }; it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result).toBeNull(); }); });
Karena saya sudah menggunakan contoh variabel options
dalam dua tes, saya memasukkannya ke dalam kode inisialisasi seluruh test suite dan menjalankan tes lagi.
Seperti yang diharapkan, metode DbMock
, properti Model
, dari kelas DbMock
mengembalikan kita undefined
, jadi tentu saja permintaan kami tidak memiliki cara untuk memanggil metode where
.
Saya sudah mengerti bahwa properti Model
dari kelas DbMock
harus diimplementasikan di luar kelas DbMock
. Pertama-tama, karena fakta bahwa implementasi
diperlukan untuk menjalankan tes yang ada, itu akan memerlukan terlalu banyak ruang bersarang ketika menginisialisasi properti Model
langsung di kelas DbMock
. Akan sangat mustahil untuk membaca dan memahami ... Dan ini, bagaimanapun, tidak akan menghentikan saya dari upaya seperti itu, karena saya ingin memastikan bahwa saya masih memiliki kesempatan untuk menulis hanya beberapa baris kode dan membuat tes berjalan dengan sukses.
Jadi Tarik napas, hembuskan, jantung yang samar meninggalkan ruangan. Melengkapi implementasi konstruktor DbMock
. Ta-daaaammmmm ....
class DbMock { constructor(){ this.Model = { extend: () => { return { where: () => { return { fetch: () => { return new Promise((resolve, reject) => { resolve(null); }); } }; } }; } }; } } module.exports = DbMock;
Timah! Namun, dengan tangan yang kuat, jalankan tes dan pastikan bahwa Jasmine
lagi menunjukkan kepada kita titik-titik hijau. Dan itu berarti kita masih di jalur yang benar, meskipun ada sesuatu yang membengkak dengan tidak tepat.
Apa selanjutnya Dapat dilihat dengan mata telanjang bahwa properti Model
pseudo-database harus diimplementasikan sebagai sesuatu yang benar-benar terpisah. Meskipun begitu saja tidak jelas bagaimana itu harus dilaksanakan.
Tapi saya tahu pasti bahwa saya akan menyimpan catatan dalam pseudo-database ini sekarang di array yang paling umum. Dan karena untuk tes yang ada saya hanya perlu mensimulasikan tabel users
, untuk permulaan saya akan mengimplementasikan array pengguna, dengan satu catatan. Tapi pertama-tama, saya akan menulis tes:
describe('Users', () => { const users = require('./Users'); it('should contain one user', () => { expect(Array.isArray(users)).toBeTruthy(); expect(users.length).toEqual(1); const user = users[0]; expect(user.Id).toEqual(1); expect(user.Name).toEqual('Jack'); }); });
Saya menjalankan tes. Saya memastikan bahwa mereka tidak lulus, dan saya menerapkan wadah sederhana saya dengan pengguna:
const Users = [ { Id: 1, Name: 'Jack' } ]; module.exports = Users;
Sekarang tes sedang dieksekusi, dan terpikir oleh saya bahwa secara semantik Model
, dalam paket bookshell
, adalah penyedia antarmuka untuk mengakses isi tabel dalam database. Tidak sia-sia kita melewatkan objek dengan nama tabel ke metode extend
. Kenapa disebut extend
, dan bukan misalnya get
, saya tidak tahu. Mungkin ini hanya kurangnya pengetahuan tentang API bookshell
.
Ya Tuhan memberkati dia, untuk saat ini saya punya ide di kepala saya pada topik tes berikut:
describe('TableMock', () => { const container = require('./Users'); const Table = require('./TableMock'); const users = new Table(container); it('should return first item', () => { users.fetch({ Id: 1 }).then((item) => { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); }); });
Karena pada saat ini saya membutuhkan implementasi yang hanya mensimulasikan fungsi driver penyimpanan nyata, saya memberi nama kelas yang sesuai, menambahkan akhiran Mock
:
class TableMock { constructor(container){ this.container = container; } fetch() { return new Promise((resolve, reject) => { resolve(this.container[0]); }); } } module.exports = TableMock;
Tetapi fetch
bukan satu-satunya metode yang ingin saya gunakan dalam versi pertarungan, jadi saya menambahkan satu tes lagi:
it('where-fetch chain should return first item', () => { users.where({ Id: 1 }).fetch().then((item)=> { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); });
Peluncuran yang, seperti yang diharapkan, menampilkan pesan kesalahan kepada saya. Jadi saya melengkapi implementasi TableMock
dengan metode di where
:
where(){ return this; }
Sekarang tes dilakukan dan Anda dapat beralih ke topik penerapan properti Model
di kelas DbMock
. Seperti yang sudah saya sarankan, ini akan menjadi penyedia objek contoh jenis TableMock
:
describe('TableMockMap', () => { const TableMock = require('./TableMock'); const TableMockMap = require('./TableMockMap'); const map = new TableMockMap(); it('extend should return existent TableMock', () => { const users = map.extend({tableName: 'users'}); expect(users instanceof TableMock).toBeTruthy(); }); });
Kenapa TableMockMap
, karena semantik ini dia. Hanya alih-alih nama metode get
, nama metode extended digunakan.
Karena tes macet, kami membuat implementasi:
const Users = require('./Users'); const TableMock = require('./TableMock'); class TableMockMap extends Map{ constructor(){ super(); this.set('users', Users); } extend(options){ const container = this.get(options.tableName); return new TableMock(container); } } module.exports = TableMockMap;
Jalankan tes dan lihat enam titik hijau di konsol. Hidup itu indah.
Tampaknya bagi saya sekarang bahwa Anda sudah dapat menyingkirkan
inisialisasi yang
di konstruktor kelas DbMock
, menggunakan TableMockMap
luar TableMockMap
. Kami tidak akan menundanya, terutama karena sudah baik untuk minum teh. Implementasi baru ini sangat elegan:
const TableMockMap = require('./TableMockMap'); class DbMock { constructor(){ this.Model = new TableMockMap(); } } module.exports = DbMock;
Jalankan tes ... dan oops! Tes kami yang paling penting jatuh. Tapi ini bahkan bagus, karena itu adalah rintisan pengujian dan sekarang kita hanya perlu memperbaikinya:
it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); });
Tes berhasil diselesaikan. Dan sekarang Anda dapat beristirahat, dan kemudian kembali untuk menyelesaikan kode permintaan yang dihasilkan, karena masih sangat, sangat jauh dari sempurna, tetapi bahkan dari hanya antarmuka yang mudah digunakan, terlepas dari kenyataan bahwa data yang menggunakannya berasal dari basis sudah bisa diterima.