Problem

Komandada ya demək olar ki, test yoxdur və hər release qumardır, ya da min-min yavaş, təsadüfi sıradan çıxan və heç kimin güvənmədiyi test var. Hər iki vəziyyətin kökü eynidir: testlər strategiyasız yazılıb. Test dəstinin işi var: vacib hesab etdiyiniz davranışın hələ də işlədiyini sürətli və etibarlı bildirmək, işləmədikdə isə səbəbə işarə etmək.

Piramida qanun yox, bələdçidir

Test piramidası deyir: aşağıda çoxlu sürətli, kiçik testlər; yuxarıda daha az, daha geniş və yavaş testlər; ən üstdə çox az end-to-end test. Əsaslandırma iqtisadidir. Kiçik testlər işləməsi ucuzdur və uğursuzluqda dəqiqdir; geniş testlər hər test üçün daha çox əminlik verir, amma yavaşdır və diaqnoz çətindir.

Onu çatılmalı nisbət kimi tətbiq etməyin. Əsasən SQL və mapping-dən ibarət servisdə təmiz məntiq azdır və ən dəyərli testlər real bazaya qarşı integration testlər ola bilər. Qiymətləndirmə qaydalarla dolu servis çoxlu unit testdən qazanır. Riskin harada olduğuna baxın.

Unit testlər

Unit test sinfi və ya kiçik sinif qrupunu Spring, baza və şəbəkə olmadan yoxlayır. Millisaniyələrlə başlayır.

class PriceCalculatorTest {    private final PriceCalculator calculator = new PriceCalculator();    @Test    void appliesTenPercentDiscountToOrdersOverThreshold() {        var order = anOrder().withSubtotal("200.00").build();        var total = calculator.total(order);        assertThat(total).isEqualByComparingTo("180.00");    }}

Biznes qaydalarını constructor ilə inject olunan asılılıqları olan adi siniflərdə saxlayın ki, bu cür test etmək asan olsun. Qaydanı test etmək üçün @SpringBootTest lazımdırsa, qayda çox güman ki, infrastrukturla dolaşıb.

Slice testlər

Spring Boot kontekstin yalnız bir hissəsini yükləyən slice-lar təklif edir.

@WebMvcTest controller-ləri, JSON çevirməni, validation-ı və exception handler-ləri yükləyir, başqa heç nəyi. Asılılıqlar mock ilə əvəz olunur (@MockitoBean Spring Framework 6.2 / Spring Boot 3.4 və daha yeni versiyaları tələb edir; daha köhnə Boot 3.x versiyalarında əvəzinə @MockBean istifadə edin):

@WebMvcTest(OrderController.class)class OrderControllerTest {    @Autowired MockMvc mvc;    @MockitoBean OrderService service;    @Test    void rejectsOrderWithoutItems() throws Exception {        mvc.perform(post("/orders")                .contentType(MediaType.APPLICATION_JSON)                .content("{\"items\":[]}"))           .andExpect(status().isBadRequest());    }}

Bu, HTTP müqaviləsini yoxlayır: status kodları, validation, serializasiya. @DataJpaTest JPA qatını və repository-ləri yükləyir. Custom sorğuları, mapping-ləri və constraint-ləri test etmək üçün düzgün yerdir, tercihən production ilə eyni baza mühərriki ilə, çünki H2 SQL dialekti və davranışına görə PostgreSQL-dən fərqlənir. Slice testlər tam tətbiq başlatmaqdan sürətlidir və səbəbə yaxın yerdə sıradan çıxır.

Integration testlər

@SpringBootTest tam konteksti başladır və hissələrin birlikdə işlədiyini yoxlayır: controller, service, repository, transaction idarəsi, security konfiqurasiyası. Onlardan məhdud sayda vacib axın üçün istifadə edin, məsələn sifarişin başdan sona yaradılması. Mühiti dürüst saxlamaq üçün onları PostgreSQL konteyneri kimi real infrastrukturla işlədin. Brauzer və ya yerləşdirilmiş sistem üzərindən tam end-to-end testləri bir ovuc kritik ssenari üçün saxlayın.

Nəyi mock etməli

Sahibi olmadığınız və ya yavaş, qeyri-deterministik, yan təsirli şeyləri mock edin: xarici HTTP API, ödəniş provayderi, email göndərici, saat. Test olunan sinfi, value obyektləri və sadə yaddaşdakı asılılıqları mock etməyin. Həddindən artıq mock implementasiyanı təkrarlayan testlər yaradır: kodun strukturunu dəyişirsiniz və davranış eyni qalsa da testlər sınır. Sorğu və ya mapping sına bilən yer olduqda, mock repository əvəzinə real bazaya üstünlük verin. Zaman üçün biznes kodunda Instant.now() çağırmaq əvəzinə Clock inject edin.

Adlandırma və struktur

Test adı davranışı və şərti təsvir etməlidir ki, uğursuzluq hesabatı cümlə kimi oxunsun: rejectsOrderWithoutItems, returnsNotFoundForUnknownCustomer, retriesOnceWhenProviderTimesOut. Hər testi arrange, act, assert kimi qurun, mərhələlər arasında boş sətirlə. Bir test, uğursuzluq üçün bir səbəb; eyni nəticə haqqında bir neçə assertion normaldır.

Test data builder-ləri

Böyük constructor-lar və kopyalanmış hazırlıq testləri küylü edir. Builder məntiqli default-lar verir və hər testə yalnız əhəmiyyətli olanı bildirməyə imkan yaradır:

public class OrderBuilder {    private String customerId = "c-1";    private BigDecimal subtotal = new BigDecimal("50.00");    public static OrderBuilder anOrder() { return new OrderBuilder(); }    public OrderBuilder withSubtotal(String v) {        this.subtotal = new BigDecimal(v); return this;    }    public Order build() { return new Order(customerId, subtotal); }}

Yeni məcburi sahə əlavə olunduqda yüz testi yox, builder-i bir dəfə dəyişirsiniz.

Tipik səhvlər

  • Qaydalarınız əvəzinə getter-ləri və framework davranışını test etmək.
  • Testlər arasında dəyişən vəziyyəti bölüşmək; nəticələr sıradan asılı olur. Hər test öz datasını hazırlamalı və təmizləməlidir.
  • Asinxron işi gözləmək üçün Thread.sleep istifadə etmək; əvəzinə timeout ilə polling istifadə edin.
  • Log çıxışına və ya private metodlara assertion yazmaq.
  • Qeyri-sabit (flaky) testlərin qırmızı və ya ignore qalmasına icazə vermək. Bəzən sıradan çıxan test komandaya uğursuzluqlara məhəl qoymamağı öyrədir.
  • Coverage faizinin arxasınca getmək. Coverage hansı kodun işlədiyini göstərir, nəyinsə yoxlanıldığını yox.

Güzəştlər

Daha çox integration test daha çox realizm və daha yavaş əks-əlaqə deməkdir. Daha çox mock daha sürətli testlər və yalançı əminlik riski deməkdir. Risk üzrə seçin: təmiz məntiq unit test, HTTP və persistence müqavilələri slice, bir neçə kritik axın tam integration test alır. Dəst yavaşlayanda və ya səhvlər boşluqdan keçəndə balansı yenidən nəzərdən keçirin.

Testləri CI-da sağlam saxlamaq

Test dəsti yalnız sürətli və sabit olduqda faydalıdır. Unit və slice testləri hər commit-də, integration testləri isə ayrıca mərhələdə işlədin ki, əks-əlaqə gec gəlməsin. Yavaş testləri izləyin: ən yavaş on testin siyahısı çox vaxt hansı birinin yalnız kontekst yenidən başlatdığını göstərir. Fərqli @MockitoBean kombinasiyaları Spring kontekstinin keşini pozur və hər kombinasiya yeni kontekst başlatmaq deməkdir; mümkün olduqda ortaq baza konfiqurasiyası istifadə edin.

Uğursuz test aydın mesaj verməlidir. Assertion kitabxanasının təsviri ilə (assertThat(...).as("...")) və mənalı test adı ilə sıradan çıxmanın səbəbini oxuyan şəxs kodu açmadan anlaya bilər.

Hardan başlamalı

Testi olmayan mövcud layihədə hər şeyi bir dəfəyə əhatə etməyə çalışmayın. Ən çox pul, risk və ya şikayət gətirən axını seçin və onun üçün bir integration test yazın. Sonra hər səhv düzəldildikdə əvvəlcə onu təkrar edən test yazın; beləcə dəst real uğursuzluqlardan böyüyür. Yeni kod üçün unit testləri adi vərdiş edin, kontrakt dəyişikliklərində isə controller slice-ı yeniləyin. Bu tədricən yanaşma bir böyük "test layihəsindən" daha çox davam edir.

Nəhayət, testlərə kod kimi yanaşın: onları da refaktor edin, təkrarları çıxarın və review-da diqqətlə oxuyun. Pis yazılmış test texniki borca çevrilir və komanda onu silməyə və ya ignore etməyə başlayır.

Yoxlama siyahısı

  • Biznes qaydaları Spring olmadan test oluna bilir.
  • Controller-lərin status kodları və validation üçün @WebMvcTest testləri var.
  • Custom sorğulu repository-lər real baza mühərrikinə qarşı test olunur.
  • Kiçik integration test dəsti əsas axınları əhatə edir.
  • Zaman, təsadüfilik və xarici çağırışlar nəzarətdədir.
  • Test adları davranışı təsvir edir; data builder-lərdən gəlir.

Növbəti addım

Aydın qatlar testi asanlaşdırır, bax: Spring Boot layering. Validation və xəta cavabları controller slice-da test etməyə dəyər, bax: validation və xəta idarəsi. Yaxşı persistence testi sorğuların sayını da yoxlaya bilər, bax: N+1 problemi.