Aplikasi TDD pada Spring Boot: bekerja dengan database

Artikel kedua dari seri "Test-Driven Development of application on Spring Boot" dan kali ini saya akan berbicara tentang pengujian akses ke database, aspek penting dari pengujian integrasi. Saya akan memberi tahu Anda cara menentukan antarmuka layanan di masa mendatang untuk akses data melalui pengujian, cara menggunakan basis data di dalam memori untuk pengujian, bekerja dengan transaksi, dan mengunggah data pengujian ke basis data.


Saya tidak akan banyak bicara tentang TDD dan pengujian secara umum, saya mengundang semua orang untuk membaca artikel pertama - Bagaimana membangun piramida di bagasi atau Test-Driven Development aplikasi pada Spring Boot / majalah geek


Saya akan mulai, seperti terakhir kali, dengan bagian teoretis kecil, dan beralih ke tes ujung ke ujung.


Menguji Piramida


Untuk mulai dengan, deskripsi kecil tetapi perlu dari entitas yang penting dalam pengujian sebagai Piramida Uji atau piramida pengujian .


gambar


(diambil dari Piramida Tes Praktis )


Piramida pengujian adalah pendekatan ketika tes diselenggarakan pada beberapa tingkatan.


  • Tes UI (atau end-to-end, E2E ) sedikit dan mereka lambat, tetapi mereka menguji aplikasi yang sebenarnya - tidak ada tiruan dan rekan tes. Bisnis sering berpikir pada tingkat ini dan semua kerangka kerja BDD tinggal di sini (lihat Mentimun di artikel sebelumnya).
  • Mereka diikuti oleh tes integrasi (layanan, komponen - masing-masing memiliki terminologi sendiri), yang sudah fokus pada komponen tertentu (layanan) sistem, mengisolasinya dari komponen lain melalui moki / ganda, tetapi masih memeriksa integrasi dengan sistem eksternal nyata - tes ini terhubung ke database, kirim permintaan REST, saya bekerja dengan antrian pesan. Bahkan, ini adalah tes yang memverifikasi integrasi logika bisnis dengan dunia luar.
  • Di bagian paling bawah adalah tes unit cepat yang menguji blok kode minimum (kelas, metode) dalam isolasi lengkap.

Spring membantu dengan menulis tes untuk setiap level - bahkan untuk tes unit , meskipun ini mungkin terdengar aneh, karena di dunia unit test tidak ada pengetahuan tentang kerangka kerja yang harus ada sama sekali. Setelah menulis tes E2E, saya hanya akan menunjukkan bagaimana Spring memungkinkan bahkan hal-hal "integrasi" murni seperti pengendali untuk menguji secara terpisah.


Tapi saya akan mulai dari bagian paling atas piramida - tes UI lambat, yang dimulai dan menguji aplikasi lengkap.


Tes ujung ke ujung


Jadi, fitur baru:


Feature: A list of available cakes Background: catalogue is updated Given the following items are promoted | Title | Price | | Red Velvet | 3.95 | | Victoria Sponge | 5.50 | Scenario: a user visiting the web-site sees the list of items Given a new user, Alice When she visits Cake Factory web-site Then she sees that "Red Velvet" is available with price £3.95 And she sees that "Victoria Sponge" is available with price £5.50 

Dan di sini ada aspek yang langsung menarik - apa yang harus dilakukan dengan tes sebelumnya, tentang salam di halaman utama? Sepertinya sudah tidak relevan lagi, setelah meluncurkan situs di halaman utama sudah akan ada direktori, bukan salam. Tidak ada jawaban tunggal, saya akan mengatakan - itu tergantung pada situasinya. Tapi saran utama - jangan melekat pada tes! Hapus ketika mereka kehilangan relevansi, tulis ulang untuk membuatnya lebih mudah dibaca. Terutama tes E2E - ini seharusnya, pada kenyataannya, spesifikasi yang hidup dan saat ini . Dalam kasus saya, saya baru saja menghapus tes lama, dan menggantinya dengan yang baru, menggunakan beberapa langkah sebelumnya dan menambahkan yang tidak ada.

Sekarang saya sampai pada poin penting - pilihan teknologi untuk menyimpan data. Sesuai dengan pendekatan lean , saya ingin menunda pilihan sampai saat terakhir - ketika saya akan tahu pasti apakah model relasional atau tidak, apa persyaratan untuk konsistensi, transaksionalitas. Secara umum, ada solusi untuk ini - misalnya, penciptaan kembar uji dan berbagai penyimpanan dalam memori , tetapi sejauh ini saya tidak ingin menyulitkan artikel dan segera memilih teknologi - basis data relasional. Tetapi untuk menjaga setidaknya beberapa kemungkinan memilih database, saya akan menambahkan abstraksi - Spring Data JPA . JPA sendiri adalah spesifikasi yang cukup abstrak untuk mengakses basis data relasional, dan Spring Data membuatnya lebih mudah digunakan.


Spring Data JPA menggunakan Hibernate sebagai penyedia secara default, tetapi juga mendukung teknologi lain, seperti EclipseLink dan MyBatis. Untuk orang-orang yang tidak terlalu terbiasa dengan Java Persistence API - JPA seperti antarmuka, dan Hibernate adalah kelas yang mengimplementasikannya.

Jadi, untuk menambahkan dukungan JPA, saya menambahkan beberapa dependensi:


 implementation('org.springframework.boot:spring-boot-starter-data-jpa') runtime('com.h2database:h2') 

Sebagai basis data saya akan menggunakan H2 - basis data tertanam yang ditulis dalam Java, dengan kemampuan untuk bekerja dalam mode dalam memori.


Menggunakan Spring Data JPA, saya segera mendefinisikan antarmuka untuk mengakses data:


 interface CakeRepository extends CrudRepository<CakeEntity, String> { } 

Dan intinya:


 @Entity @Builder @AllArgsConstructor @Table(name = "cakes") class CakeEntity { public CakeEntity() { } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; @NotBlank String title; @Positive BigDecimal price; @NotBlank @NaturalId String sku; boolean promoted; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CakeEntity cakeEntity = (CakeEntity) o; return Objects.equals(title, cakeEntity.title); } @Override public int hashCode() { return Objects.hash(title); } } 

Ada beberapa hal yang kurang jelas dalam deskripsi entitas.


  • @NaturalId untuk bidang sku . Bidang ini digunakan sebagai "pengidentifikasi alami" untuk memeriksa kesetaraan entitas - menggunakan semua bidang atau bidang @Id dalam metode equals / hashCode lebih merupakan anti-pola. Ditulis dengan baik tentang cara memverifikasi dengan benar kesetaraan entitas, misalnya, di sini .
  • Untuk sedikit mengurangi kode boilerplate, saya menggunakan Project Lombok - prosesor anotasi untuk Java. Ini memungkinkan Anda untuk menambahkan berbagai hal berguna, seperti @Builder - untuk secara otomatis menghasilkan pembangun untuk kelas dan @AllArgsConstructor untuk membuat konstruktor untuk semua bidang.

Implementasi antarmuka akan disediakan secara otomatis oleh Spring Data.


Menuruni piramida


Sekarang adalah waktunya untuk turun ke tingkat piramida berikutnya. Sebagai aturan praktis, saya akan menyarankan Anda selalu mulai dengan tes e2e , karena ini akan memungkinkan Anda untuk menentukan "tujuan akhir" dan batas-batas fitur baru, tetapi tidak ada aturan ketat lebih lanjut. Tidak perlu menulis tes integrasi terlebih dahulu, sebelum pindah ke tingkat unit. Paling sering lebih nyaman dan lebih sederhana - dan itu wajar untuk turun.


Tetapi khusus sekarang, saya ingin segera melanggar aturan ini dan menulis unit test yang akan membantu menentukan antarmuka dan kontrak komponen baru yang belum ada. Pengontrol harus mengembalikan model yang akan diisi dari komponen X tertentu, dan saya menulis tes ini:


 @ExtendWith(MockitoExtension.class) class IndexControllerTest { @Mock CakeFinder cakeFinder; @InjectMocks IndexController indexController; private Set<Cake> cakes = Set.of(new Cake("Test 1", "£10"), new Cake("Test 2", "£10")); @BeforeEach void setUp() { when(cakeFinder.findPromotedCakes()).thenReturn(cakes); } @Test void shouldReturnAListOfFoundPromotedCakes() { ModelAndView index = indexController.index(); assertThat(index.getModel()).extracting("cakes").contains(cakes); } } 

Ini adalah unit test murni - tidak ada konteks, tidak ada database di sini, hanya Mockito untuk mok. Dan tes ini hanyalah demonstrasi yang bagus tentang bagaimana Spring membantu pengujian unit - controller di Spring MVC hanyalah kelas yang metodenya menerima parameter tipe biasa dan mengembalikan objek POJO - Lihat Model . Tidak ada permintaan HTTP, tidak ada respons, header, JSON, XML - semua ini akan secara otomatis diterapkan di tumpukan dalam bentuk konverter dan serializer. Ya, ada "petunjuk" kecil untuk Spring dalam bentuk ModelAndView , tetapi ini adalah POJO biasa dan Anda bahkan dapat menyingkirkannya jika diinginkan, diperlukan khusus untuk pengontrol UI.


Saya tidak akan berbicara banyak tentang Mockito, Anda dapat membaca semuanya di dokumentasi resmi. Secara khusus, hanya ada poin menarik dalam tes ini - saya menggunakan MockitoExtension.class sebagai MockitoExtension.class uji, dan itu akan secara otomatis menghasilkan mokas untuk bidang yang dijelaskan oleh @Mock dan kemudian menyuntikkan mokas ini sebagai dependensi dalam konstruktor untuk objek di bidang yang ditandai @InjectMocks . Anda dapat melakukan semua ini secara manual menggunakan metode Mockito.mock() dan kemudian membuat kelas.

Dan tes ini membantu menentukan metode komponen baru - findPromotedCakes , daftar kue yang ingin kami tampilkan di halaman utama. Dia tidak menentukan apa itu, atau bagaimana seharusnya bekerja dengan database. Tanggung jawab pengontrol adalah untuk mengambil apa yang ditransfer ke sana dan mengembalikan model ("kue") di bidang tertentu. Namun demikian, CakeFinder sudah memiliki metode pertama di antarmuka saya, yang berarti Anda dapat menulis tes integrasi untuknya.


Saya sengaja membuat semua kelas di dalam paket cakes pribadi sehingga tidak ada orang di luar paket yang bisa menggunakannya. Satu-satunya cara untuk mendapatkan data dari database adalah dengan antarmuka CakeFinder, yang merupakan "komponen X" saya untuk mengakses database. Ini menjadi "konektor" alami, yang dapat dengan mudah saya kunci jika saya perlu menguji sesuatu secara terpisah dan tidak menyentuh alasnya. Dan satu-satunya implementasinya adalah JpaCakeFinder. Dan jika, misalnya, tipe basis data atau sumber data berubah di masa mendatang, maka Anda perlu menambahkan implementasi antarmuka CakeFinder tanpa mengubah kode yang menggunakannya.

Tes integrasi untuk JPA menggunakan @ DataJpaTest


Tes integrasi adalah roti dan mentega Spring. Di dalamnya, pada kenyataannya, semuanya dilakukan dengan sangat baik untuk pengujian integrasi sehingga pengembang terkadang tidak ingin pergi ke tingkat unit atau mengabaikan tingkat UI. Ini tidak buruk atau baik - saya ulangi bahwa tujuan utama dari tes ini adalah kepercayaan diri. Dan satu set tes integrasi yang cepat dan efektif mungkin cukup untuk memberikan kepercayaan ini. Namun, ada bahaya bahwa seiring waktu, tes ini akan menjadi lebih lambat atau lebih lambat, atau mulai menguji komponen secara terpisah, bukan integrasi.


Tes integrasi dapat menjalankan aplikasi apa adanya ( @SpringBootTest ), atau komponennya yang terpisah (JPA, Web). Dalam kasus saya, saya ingin menulis tes terfokus untuk JPA - jadi saya tidak perlu mengonfigurasi pengontrol atau komponen lainnya. Anotasi @DataJpaTest bertanggung jawab atas hal ini di Uji Booting Musim Semi. Ini adalah meta anotasi, mis. Ini menggabungkan beberapa anotasi berbeda yang mengkonfigurasi berbagai aspek pengujian.


  • @AutoConfigureDataJpa
  • @AutoConfigureTestDatabase
  • @AutoConfigureCache
  • @AutoConfigureTestEntityManager
  • @Transaksional

Pertama saya akan memberitahu Anda tentang masing-masing secara individual, dan kemudian saya akan menunjukkan kepada Anda tes selesai.


@AutoConfigureDataJpa
Itu memuat seluruh rangkaian konfigurasi dan mengatur repositori (pembuatan otomatis implementasi untuk CrudRepositories ), alat migrasi untuk database FlyWay dan Liquibase, menghubungkan ke database menggunakan DataSource, manajer transaksi, dan akhirnya Hibernate. Pada kenyataannya, ini hanya satu set konfigurasi yang relevan untuk mengakses data - baik DispatcherServlet dari Web MVC, maupun komponen lainnya termasuk di sini.


@AutoConfigureTestDatabase
Ini adalah salah satu aspek paling menarik dari tes JPA. Konfigurasi ini mencari classpath untuk salah satu database tertanam yang didukung dan mengkonfigurasi ulang konteksnya sehingga DataSource menunjuk ke database dalam-memori yang dibuat secara acak . Karena saya menambahkan ketergantungan pada basis H2, saya tidak perlu melakukan hal lain, hanya memiliki anotasi ini secara otomatis untuk setiap uji coba akan memberikan basis kosong, dan ini hanya sangat nyaman.


Perlu diingat bahwa pangkalan ini akan benar-benar kosong, tanpa skema. Untuk menghasilkan rangkaian, ada beberapa opsi.


  1. Gunakan fitur Auto DDL dari Hibernate. Spring Boot Test akan secara otomatis mengatur nilai ini menjadi create-drop sehingga Hibernate akan menghasilkan skema dari deskripsi entitas dan menghapusnya di akhir sesi. Ini adalah fitur Hibernate yang sangat kuat, yang sangat berguna untuk pengujian.
  2. Gunakan migrasi yang dibuat oleh Flyway atau Liquibase .

Anda dapat membaca lebih lanjut tentang berbagai pendekatan untuk menginisialisasi database dalam dokumentasi .


@AutoConfigureCache
Itu hanya mengkonfigurasi cache untuk menggunakan NoOpCacheManager - yaitu jangan tembolok apa pun. Ini berguna untuk menghindari kejutan dalam tes.


@AutoConfigureTestEntityManager
Menambahkan objek TestEntityManager khusus ke TestEntityManager , yang dengan sendirinya merupakan binatang yang menarik. EntityManager adalah kelas utama JPA, yang bertanggung jawab untuk menambahkan entitas ke sesi, menghapus, dan hal-hal serupa. Hanya ketika, misalnya, Hibernate mulai beroperasi - menambahkan entitas ke sesi tidak berarti bahwa permintaan ke database akan dieksekusi, dan memuat dari sesi tidak berarti bahwa permintaan pilih akan dieksekusi. Karena mekanisme internal Hibernate, operasi nyata dengan database akan dilakukan pada waktu yang tepat, yang akan ditentukan oleh kerangka itu sendiri. Tetapi dalam pengujian, mungkin perlu untuk mengirim sesuatu ke database dengan paksa, karena tujuan dari tes ini adalah untuk menguji integrasi. Dan TestEntityManager hanyalah pembantu yang akan membantu beberapa operasi dengan database dilakukan secara paksa - misalnya, persistAndFlush() akan memaksa Hibernate untuk menjalankan semua permintaan.


@Transaksional
Anotasi ini membuat semua tes dalam transaksional kelas, dengan rollback otomatis dari transaksi setelah menyelesaikan tes. Ini hanya mekanisme untuk "membersihkan" database sebelum setiap tes, karena jika tidak, Anda harus menghapus data secara manual dari setiap tabel.


Apakah tes harus mengelola transaksi bukanlah pertanyaan yang sederhana dan jelas seperti kelihatannya. Terlepas dari kenyamanan basis data "bersih", kehadiran @Transactional dalam pengujian dapat menjadi kejutan yang tidak menyenangkan jika kode "pertempuran" tidak memulai transaksi itu sendiri, tetapi membutuhkan yang sudah ada. Ini dapat mengarah pada fakta bahwa tes integrasi lulus, tetapi ketika kode nyata dieksekusi dari controller, dan bukan dari tes, layanan tidak akan memiliki transaksi aktif dan metode akan mengeluarkan pengecualian. Meskipun ini terlihat berbahaya, dengan tes tingkat tinggi tes UI, tes transaksional tidak begitu buruk. Dalam pengalaman saya, saya hanya melihat sekali, ketika tes integrasi yang lulus crash kode produksi, yang jelas membutuhkan keberadaan transaksi yang ada. Tetapi jika Anda masih perlu memverifikasi bahwa layanan dan komponen itu sendiri mengelola transaksi dengan benar, Anda dapat "memblokir" anotasi @Transactional pada pengujian dengan mode yang diinginkan (misalnya, jangan memulai transaksi).

Uji Integrasi dengan @SpringBootTest


Saya juga ingin mencatat bahwa @DataJpaTest bukan contoh unik dari tes integrasi fokal, ada @WebMvcTest , @DataMongoTest dan banyak lainnya. Tetapi salah satu anotasi pengujian paling penting tetap @SpringBootTest , yang meluncurkan aplikasi "sebagaimana adanya" untuk pengujian - dengan semua komponen dan integrasi yang dikonfigurasi. Sebuah pertanyaan logis muncul - jika Anda dapat menjalankan seluruh aplikasi, mengapa melakukan tes DataJpa fokus, misalnya? Saya akan mengatakan bahwa tidak ada aturan ketat di sini lagi.


Jika memungkinkan untuk menjalankan aplikasi setiap saat, mengisolasi crash dalam pengujian, jangan overload dan jangan mempersulit pengaturan Setup tes, maka tentu saja Anda dapat dan harus menggunakan @SpringBootTest.


Namun, dalam kehidupan nyata, aplikasi dapat memerlukan banyak pengaturan yang berbeda, terhubung ke sistem yang berbeda, dan saya tidak ingin tes akses database saya jatuh, karena koneksi ke antrian pesan tidak dikonfigurasi. Oleh karena itu, penting untuk menggunakan akal sehat, dan jika untuk mendapatkan tes dengan penjelasan @SpringBootTest agar berfungsi, Anda perlu mengunci setengah sistem - apakah masuk akal sama sekali di @SpringBootTest?


Persiapan data untuk tes


Salah satu poin utama untuk pengujian adalah persiapan data. Setiap tes harus dilakukan dalam isolasi, dan mempersiapkan lingkungan sebelum memulai, membawa sistem ke keadaan semula yang diinginkan. Opsi termudah untuk melakukan ini adalah dengan menggunakan @BeforeEach / @BeforeAll anotasi dan menambahkan entri ke database di sana menggunakan repositori, EntityManager atau TestEntityManager . Tetapi ada opsi lain yang memungkinkan Anda untuk menjalankan skrip yang sudah disiapkan atau menjalankan query SQL yang diinginkan, ini adalah penjelasan @Sql . Sebelum menjalankan tes, Uji Booting Musim Semi akan secara otomatis menjalankan skrip yang ditentukan, menghilangkan keharusan untuk menambahkan blok @BeforeAll , dan @Transactional akan menangani @Transactional data.


 @DataJpaTest class JpaCakeFinderTest { private static final String PROMOTED_CAKE = "Red Velvet"; private static final String NON_PROMOTED_CAKE = "Victoria Sponge"; private CakeFinder finder; @Autowired CakeRepository cakeRepository; @Autowired TestEntityManager testEntityManager; @BeforeEach void setUp() { this.testEntityManager.persistAndFlush(CakeEntity.builder().title(PROMOTED_CAKE) .sku("SKU1").price(BigDecimal.TEN).promoted(true).build()); this.testEntityManager.persistAndFlush(CakeEntity.builder().sku("SKU2") .title(NON_PROMOTED_CAKE).price(BigDecimal.ONE).promoted(false).build()); finder = new JpaCakeFinder(cakeRepository); } ... } 

Siklus refactor merah-hijau


Terlepas dari jumlah teks ini, untuk pengembang, tes masih terlihat seperti kelas sederhana dengan anotasi @DataJpaTest, tapi saya harap saya dapat menunjukkan seberapa banyak hal berguna yang terjadi di bawah tenda, yang tidak dapat dipikirkan oleh pengembang. Sekarang kita dapat beralih ke siklus TDD dan kali ini saya akan menunjukkan beberapa iterasi TDD, dengan contoh-contoh refactoring dan kode minimal. Untuk membuatnya lebih jelas, saya sangat menyarankan agar Anda melihat sejarah di Git, di mana setiap komit adalah langkah yang terpisah dan signifikan dengan deskripsi tentang apa dan bagaimana fungsinya.


Persiapan data


Saya menggunakan pendekatan dengan @BeforeAll / @BeforeEach dan secara manual membuat semua catatan dalam database. Contoh dengan penjelasan @Sql dipindahkan ke kelas yang terpisah JpaCakeFinderTestWithScriptSetup , itu menggandakan tes, yang, tentu saja, tidak boleh, dan ada untuk tujuan tunggal menunjukkan pendekatan.


Keadaan awal sistem - ada dua entri dalam sistem, satu kue berpartisipasi dalam promosi dan harus dimasukkan dalam hasil yang dikembalikan oleh metode, yang kedua - tidak.


Tes integrasi pertama


Tes pertama adalah yang paling sederhana - findPromotedCakes harus menyertakan deskripsi dan harga kue yang berpartisipasi dalam promosi.


Merah


  @Test void shouldReturnPromotedCakes() { Iterable<Cake> promotedCakes = finder.findPromotedCakes(); assertThat(promotedCakes).extracting(Cake::getTitle).contains(PROMOTED_CAKE); assertThat(promotedCakes).extracting(Cake::getPrice).contains("£10.00"); } 

Tes, tentu saja, lumpuh - implementasi standar mengembalikan Set kosong.


Hijau


Secara alami, kami ingin segera menulis pemfilteran, membuat permintaan ke database dengan where dan seterusnya. Tetapi setelah latihan TDD, saya harus menulis kode minimum agar lulus . Dan kode minimal ini adalah untuk mengembalikan semua catatan dalam database. Ya, sangat sederhana dan klise.


  public Set<Cake> findPromotedCakes() { Spliterator<CakeEntity> cakes = this.cakeRepository.findAll() .spliterator(); return StreamSupport.stream(cakes, false).map( cakeEntity -> new Cake(cakeEntity.title, formatPrice(cakeEntity.price))) .collect(Collectors.toSet()); } private String formatPrice(BigDecimal price) { return "£" + price.setScale(2, RoundingMode.DOWN).toPlainString(); } 

Mungkin beberapa orang akan berpendapat bahwa di sini Anda dapat membuat tes hijau bahkan tanpa basis - hanya hardcode hasil yang diharapkan oleh tes. Saya sesekali mendengar argumen seperti itu, tetapi saya pikir semua orang mengerti bahwa TDD bukan dogma atau agama, tidak masuk akal untuk membawa ini ke titik absurditas. Tetapi jika Anda benar-benar ingin, maka Anda dapat, misalnya, mengacak data pada instalasi sehingga tidak hardcode.

Refactor


Saya tidak melihat banyak refactoring di sini, sehingga fase ini dapat dilewati untuk tes khusus ini. Tetapi saya masih tidak akan merekomendasikan mengabaikan fase ini, lebih baik untuk berhenti dan berpikir setiap kali dalam keadaan "hijau" dari sistem - apakah mungkin untuk refactor sesuatu untuk membuatnya lebih baik dan lebih mudah?


Tes kedua


Tetapi pengujian kedua sudah akan memverifikasi bahwa tidak ada kue yang dipromosikan akan jatuh ke dalam hasil yang dikembalikan oleh findPromotedCakes .


  @Test void shouldNotReturnNonPromotedCakes() { Iterable<Cake> promotedCakes = finder.findPromotedCakes(); assertThat(promotedCakes).extracting(Cake::getTitle) .doesNotContain(NON_PROMOTED_CAKE); } 

Merah


Tes, seperti yang diharapkan, crash - ada dua catatan dalam database dan kode hanya mengembalikan semuanya.


Hijau


Dan lagi Anda dapat berpikir - dan apa kode minimum yang dapat Anda tulis untuk lulus ujian? Karena sudah ada aliran dan rakitannya, Anda cukup menambahkan blok filter sana.


  public Set<Cake> findPromotedCakes() { Spliterator<CakeEntity> cakes = this.cakeRepository.findAll() .spliterator(); return StreamSupport.stream(cakes, false) .filter(cakeEntity -> cakeEntity.promoted) .map(cakeEntity -> new Cake(cakeEntity.title, formatPrice(cakeEntity.price))) .collect(Collectors.toSet()); } 

Kami memulai kembali tes - tes integrasi sekarang berwarna hijau. Momen penting telah datang - karena kombinasi uji unit controller dan uji integrasi untuk bekerja dengan database, fitur saya sudah siap - dan tes UI sekarang berlalu!


Refactor


Dan karena semua tes berwarna hijau - saatnya untuk refactor. Saya pikir tidak perlu mengklarifikasi bahwa pemfilteran dalam memori bukan ide yang baik, lebih baik melakukan ini dalam database. Untuk melakukan ini, saya menambahkan metode baru di CakesRepository - findByPromotedIsTrue :


 interface CakeRepository extends CrudRepository<CakeEntity, String> { Iterable<CakeEntity> findByPromotedIsTrue(); } 

Untuk metode ini, Spring Data secara otomatis menghasilkan metode yang akan mengeksekusi kueri pemilihan formulir select from cakes where promoted = true . Baca selengkapnya tentang pembuatan kueri dalam dokumentasi Data Musim Semi.


  public Set<Cake> findPromotedCakes() { Spliterator<CakeEntity> cakes = this.cakeRepository.findByPromotedIsTrue() .spliterator(); return StreamSupport.stream(cakes, false).map( cakeEntity -> new Cake(cakeEntity.title, formatPrice(cakeEntity.price))) .collect(Collectors.toSet()); } 

Ini adalah contoh yang baik tentang fleksibilitas yang diberikan pengujian integrasi dan pendekatan kotak hitam. Jika repositori dikunci, maka menambahkan metode baru tanpa mengubah tes bukan tidak mungkin.


Koneksi ke basis produksi


Untuk menambahkan sedikit "realisme" dan menunjukkan bagaimana Anda dapat memisahkan konfigurasi untuk pengujian dan aplikasi utama, saya akan menambahkan konfigurasi akses data untuk aplikasi "produksi".


Semuanya ditambahkan secara tradisional oleh bagian di application.yml :


 datasource: url: jdbc:h2:./data/cake-factory 

Ini secara otomatis akan menyimpan data dalam sistem file ke folder ./data . Saya perhatikan bahwa folder ini tidak akan dibuat dalam pengujian - @DataJpaTest akan secara otomatis mengganti koneksi ke file database dengan database acak dalam memori karena adanya penjelasan @AutoConfigureTestDatabase .


, — data.sql schema.sql . , Spring Boot . , , , .

Kesimpulan


, , , TDD .


Spring Security — Spring, .

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


All Articles