Pengembangan tim untuk menanyakan data dari database - bagian 2

Pada bagian sebelumnya, saya fokus pada fakta bahwa tim yang saya kembangkan menerapkan perilaku yang dapat dijelaskan dengan tes ini:


it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); }); 

Sepertinya saya sekarang yang mendapatkan Promise sebagai hasil dan memprosesnya tidak seperti yang saya inginkan. Akan lebih baik jika tim itu sendiri melakukan pekerjaan rutin ini, dan hasilnya akan ditempatkan, misalnya, dalam repositori Redux . Saya akan mencoba menulis ulang tes yang ada untuk mengungkapkan harapan baru saya di dalamnya:


 const store = require('../../src/store'); const DbMock = require('../mocks/DbMock'); const db = new DbMock(); const Request = require('../../src/storage/Request'); const options = { tableName: 'users', query: { Id: 1 } }; let request = null; beforeEach(() => { request = new Request(db, store); }); it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Ini mungkin lebih nyaman, meskipun saya sekarang harus mengajarkan metode execute dari kelas Request untuk mengeksekusi metode panggilan balik jika pengguna meneruskannya sebagai argumen. Anda tidak dapat melakukannya tanpa itu, karena execute dalam saya kira akan menggunakan panggilan asinkron, hasil pengujian yang hanya dapat diuji jika mereka yakin bahwa eksekusi mereka telah selesai.


Selanjutnya ... Melihat baris kode pertama, saya mengerti bahwa sebelum saya dapat kembali mengedit kode kelas Request , saya perlu menambahkan paket Redux ke proyek, mengimplementasikan setidaknya satu dan secara terpisah mengemas peredam ini di Store . Tes pertama mungkin untuk gearbox:


 const reduce = require('../../src/reducers/user'); it('should return new state', () => { const user = { Id: 1, Name: 'Jack'}; const state = reduce(null, {type: 'USER', user }); expect(state).toEqual(user); }); 

Saya menjalankan tes dan setuju dengan Jasmine bahwa selain semua kesalahan sebelumnya, modul dengan nama ../../src/reducers/user tidak ditemukan. Oleh karena itu, saya akan menulisnya, terutama karena ia berjanji untuk menjadi kecil dan sangat dapat diprediksi:


 const user = (state = null, action) => { switch (action.type) { case 'USER': return action.user; default: return state; } }; module.exports = user; 

Saya menjalankan tes dan tidak melihat peningkatan radikal. Ini karena modul ../../src/store , keberadaan yang saya asumsikan dalam tes untuk kelas Request saya, saya belum diimplementasikan. Dan belum ada tes untuknya. Saya akan mulai dengan tes:


 describe('store', () => { const store = require('../src/store'); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); }); 

Tes? Ada lebih banyak laporan tentang kurangnya modul store , jadi saya akan menanganinya segera.


 const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const reducers = combineReducers({user}); const store = createStore(reducers); module.exports = store; 

Memahami bahwa saya akan memiliki lebih dari satu gearbox, saya menjalankan sedikit ke depan dalam implementasi dan menggunakan metode combineReducers ketika merakitnya. Saya menjalankan tes lagi dan melihat pesan kesalahan baru yang memberi tahu saya bahwa metode execute kelas Request saya tidak berfungsi seperti yang disarankan tes saya. Sebagai hasil dari metode eksekusi, catatan pengguna tidak muncul di . Sudah waktunya untuk memperbaiki kelas Request .


Saya ingat bagaimana sekarang tes dari metode execute terlihat seperti:


 it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Dan saya akan memperbaiki kode metode itu sendiri sehingga tes memiliki kesempatan untuk dieksekusi:


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.store.dispatch({ type: 'USER', user: item }); if(typeof callback === 'function') callback(); }); } 

Saya akan mengetikkan npm test console dan ... Bingo! Permintaan saya belajar tidak hanya untuk menerima data dari database, tetapi juga untuk menyimpannya dalam proses pemrosesan di masa depan, sehingga operasi selanjutnya dapat menerima data ini tanpa masalah.


Tapi! Pawang saya hanya dapat mengirim satu jenis tindakan ke , dan ini sangat membatasi kemampuannya. Tapi saya ingin menggunakan kode ini berulang kali setiap kali saya perlu mengekstrak beberapa catatan dari database dan mengirimkannya ke sel untuk diproses lebih lanjut di bawah kunci yang saya butuhkan. Maka saya mulai refactoring tes lagi:


 const options = { tableName: 'users', query: { Id : 1 }, success (result, store) { const type = 'USER'; const action = { type , user: result }; store.dispatch(action); } }; it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Terpikir oleh saya bahwa akan lebih baik untuk menyimpan kelas Request dari fungsi yang tidak biasa untuk memproses hasil permintaan. Request semantik adalah permintaan. Memenuhi permintaan, menerima jawaban, tugas selesai, prinsip tanggung jawab tunggal kelas dihormati. Dan biarkan seseorang yang terlatih secara khusus dalam proses ini menangani hasilnya, yang tanggung jawabnya seharusnya adalah versi tertentu dari proses itu sendiri. Oleh karena itu, saya memutuskan untuk mentransfer metode success ke pengaturan permintaan, yang merupakan tugas memproses data yang berhasil dikembalikan oleh permintaan.


Tes, sekarang Anda tidak bisa lari. Saya mengerti itu secara intelektual. Saya tidak memperbaiki apa pun dalam tes itu sendiri dan tidak mengubah apa pun dalam implementasi, dan tes harus terus berjalan dengan sukses. Tetapi secara emosional, saya perlu menjalankan perintah npm test dan saya menjalankannya, dan saya melanjutkan untuk mengedit implementasi metode execute saya di kelas Request untuk mengganti baris dengan panggilan ke store.dispatch(...) dengan baris dengan panggilan ke this.options.success(...) :


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback !== 'undefined') callback(); }); } 

Saya menjalankan tes. Voila! Tes sepenuhnya hijau. Hidup menjadi lebih baik! Apa selanjutnya Segera saya melihat bahwa Anda perlu mengubah judul tes, karena itu tidak cukup sesuai dengan kenyataan. Tes tidak memeriksa bahwa pengiriman metode terjadi sebagai hasil dari permintaan, tetapi permintaan memperbarui keadaan dalam wadah. Oleh karena itu, saya mengubah judul tes menjadi ... yah, misalnya:


 it('should update store user state if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); }); 

Apa selanjutnya Dan kemudian saya pikir sudah waktunya untuk memperhatikan bukan kasus ketika, alih-alih data yang diminta, permintaan saya akan mengembalikan kesalahan. Ini bukan skenario yang mustahil. Benar? Dan hal utama adalah bahwa dalam hal ini, saya tidak akan dapat menyiapkan dan mengirim set data yang diperlukan ke operator KYC saya, demi integrasi dengan mana saya menulis semua kode ini. Benarkah begitu? Jadi Pertama saya akan menulis tes:


 it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); }); 

Saya tidak tahu apakah struktur tes menunjukkan bahwa saya memutuskan untuk menghemat waktu dan uang dan menulis kode minimum untuk memeriksa apakah permintaan mengembalikan kesalahan? Tidak melihat


Saya tidak ingin membuang waktu coding implementasi tambahan TableMock , yang akan meniru kesalahan. Saya memutuskan bahwa saat ini beberapa konstruksi kondisional dalam implementasi yang ada akan cukup bagi saya, dan menyarankan bahwa ini dapat disesuaikan melalui parameter kueri query . Jadi asumsi saya adalah:


  • Jika Id dalam kueri options.query adalah 1 , maka pseudo-table saya selalu mengembalikan Promise diizinkan dengan catatan pertama dari koleksi.
  • Jika Id dalam kueri options.query adalah 555 , maka pseudo-table saya selalu mengembalikan Promise ditolak dengan contoh Error di dalam, yang konten message Ada yang salah! .

Tentu saja, ini jauh dari ideal. Akan jauh lebih mudah dibaca dan nyaman bagi persepsi untuk menerapkan contoh DbMock sesuai, baik, misalnya, FaultyDbMock , EmptyDbMock , EmptyDbMock . Dari nama-nama yang segera jelas bahwa yang pertama akan selalu berfungsi dengan benar, yang kedua akan selalu bekerja secara salah, dan untuk yang ketiga, kita dapat mengasumsikan bahwa ia akan selalu mengembalikan null alih-alih hasilnya. Mungkin, setelah memeriksa asumsi pertama saya dengan cara yang disebutkan di atas, bahwa bagi saya tampaknya mengambil waktu minimum, saya akan DbMock implementasi dua contoh tambahan DbMock yang mensimulasikan perilaku tidak sehat .


Saya menjalankan tes. Saya mendapatkan kesalahan yang diharapkan dari kurangnya properti yang saya butuhkan di dan ... Saya sedang menulis tes lain. Kali ini untuk gearbox yang akan menangani aksi dengan tipe ERROR .


 describe('error', () => { const reduce = require('../../src/reducers/error'); it('should add error to state array', () => { const type = 'ERROR'; const error = new Error('Oooops!'); const state = reduce(undefined, { type, error }); expect(Array.isArray(state)).toBeTruthy(); expect(state.length).toEqual(1); expect(state.includes(error)).toBeTruthy(); }); }); 

Menjalankan tes lagi. Semuanya diharapkan, satu lagi telah ditambahkan ke kesalahan yang ada. Saya menyadari :


 const error = (state = [], action) => { switch (action.type) { case 'ERROR': return state.concat([action.error]); default: return state; } }; module.exports = error; 

Sekali lagi, jalankan tes. Peredam baru berfungsi seperti yang diharapkan, tapi saya masih perlu memastikan bahwa peranti itu terhubung ke repositori dan memproses tindakan yang memang dimaksudkan. Oleh karena itu, saya menulis tes tambahan untuk suite tes penyimpanan yang ada:


 it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); }); 

Saya menjalankan tes. Segalanya diharapkan. Tindakan dengan tipe ERROR tidak memproses penyimpanan yang ada. Mengubah kode inisialisasi repositori yang ada:


 const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const error = require('./reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); module.exports = store; 

Untuk keseratus kalinya dia melempar jaring ... Bagus sekali! Repositori sekarang mengakumulasi pesan kesalahan yang diterima di properti kontainer yang terpisah.


Sekarang saya akan memperkenalkan beberapa konstruksi kondisional ke dalam implementasi TableMock ada, mengajarkannya untuk memulai beberapa pertanyaan dengan cara ini, mengembalikan kesalahan. Kode yang diperbarui terlihat seperti ini:


 class TableMock { constructor(array){ this.container = array; } where(query){ this.query = query; return this; } fetch(){ return new Promise((resolve, reject) => { if(this.query.Id === 1) return resolve(this.container[0]); if(this.query.Id === 555) return reject(new Error('Something goes wrong!')); }); } } module.exports = TableMock; 

Saya menjalankan tes dan mendapatkan pesan tentang penolakan Promise tidak tertangani dalam metode execute dari kelas Request . Saya menambahkan kode yang hilang:


 execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback === 'function') callback(); }).catch((error) => { this.options.error(error, this.store); if(typeof callback === 'function') callback(); }); } 

Dan saya menjalankan tes lagi. Dan ??? Sebenarnya tidak ada tes untuk metode execute dari kelas Request , yang ini:


 it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); }); 

Ia berhasil menyelesaikan. Jadi fungsionalitas kueri dalam hal penanganan kesalahan dapat dipertimbangkan diimplementasikan. Tes lain jatuh, yang memeriksa repositori untuk penanganan kesalahan. Masalahnya adalah modul implementasi penyimpanan saya mengembalikan instance penyimpanan statis yang sama ke semua konsumen dalam semua pengujian. Dalam hal ini, karena pengiriman kesalahan sudah terjadi dalam dua pengujian, salah satunya verifikasi jumlah kesalahan dalam wadah belum tentu lulus. Karena pada saat tes dimulai, sudah ada satu kesalahan dalam wadah dan satu lagi ditambahkan di sana selama peluncuran uji. Jadi di sini adalah kode ini:


 const error = store.getState().error; expect(error.length).toEqual(1); 

Melempar pengecualian, melaporkan bahwa error.length ekspresi. error.length sebenarnya 2, bukan 1. Masalah ini sekarang saya akan pecahkan hanya dengan mentransfer kode inisialisasi penyimpanan langsung ke kode inisialisasi tes penyimpanan:


 describe('store', () => { const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('../src/reducers/user'); const error = require('../src/reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); }); }); 

Kode inisialisasi untuk tes sekarang terlihat sedikit bengkak, tetapi saya dapat kembali ke refactoring nanti.


Saya menjalankan tes. Voila! Semua tes telah selesai dan Anda dapat beristirahat.

Source: https://habr.com/ru/post/id436348/


All Articles