Java tətbiqetməsinin davamlı çatdırılmasına necə nail olmaq olar - I hissə

Davamlı inteqrasiya, fasiləsiz çatdırılma və fasiləsiz yerləşdirmə indi zəruridir.

Giriş

Bu oxşar terminlər DevOps sahəsində geniş yayılmışdır. Bu blog yazı üçün, Davamlı Çatdırılma və daha dəqiq bir Java tətbiqi üçün Davamlı Çatdırılma yollarına diqqət yetirəcəyik. Yalnız müasir Java tətbiqetmələrinə yönəlmiş olsaq da, bu, xidmət proqramları kimi monolit, çoxsaylı mikroservis və ya serversiz stil funksiyalarından fərqli bir çox fürsətə çevrilə bilər.

İndi sürətli bir görüntüyə sahib olmalı və davamlı Çatdırılmanın başında bir görüntü yaratmalıyıq. Məni təsvir etmək üçün ən asan müqayisə yolu, inkişaf etdiricilərinizin əsas layihəni heç vaxt pozmamalarını təmin etməklə, bütün növ yeni proqram dəyişikliyini, yeni xüsusiyyətlərdən səhv düzəltmələrinə qədər etibarlı və davamlı şəkildə azad etməyə imkan verən proseslər dəsti kimi təsəvvür etməkdir. , onu daima yerləşdirilə bilən vəziyyətdə saxlamaq. Çatdırılma qrupu baxımından düşünmək istəsəniz, qısa müddət ərzində dəyərli və sağlam bir proqram istehsal etmək üçün həmin komandada proqramın verildiyi təcrübə və fənləri təmsil edir. Bu, proqram təminatının inkişafı dünyası və iş tələbləri son 10 ildə yeniliyə, sürətə və bazara vaxta böyük diqqət ayırmaqla dəyişdiyinə görə uğur üçün əsas işarədir.

Növbəti addım “Niyə?” Sualına cavab verməkdir. işin nə üçün buna vaxt və pul xərcləməsi deyil, nə üçün bir Java inkişaf etdiricisinin davamlı çatdırılma və hərtərəfli qurma boru kəmərlərini əhatə etməsi lazımdır. Mən Whys-ə cavab vermək üçün davamlı çatdırılmanın bir neçə əsas nöqtəsini və üstünlüklərini araşdıracağam:

Sürətli rəy

Konteksti geliştirici baxımından keçidini azaldır və inkişaf etdiriciyə nisbətən kiçik olduqda və həll etmək asandır problemlərin aşkarlanmasına və həll edilməsinə imkan verir, yəni hələ də ağlınızda təzə olanda bunu düzəldə bilərsiniz.

Biznes üçün rəqabət üstünlüyü, getdikcə daha çox yer böyük layihələrdən uzaqlaşmağa və relizlər arasında qısa dövrəyə qədər davam etməyə başlayır və məhsulun istiqamətini daha sürətli istiqamətləndirmək və məhsulun əsas fəaliyyət göstəricilərini təyin etmək üçün istifadəçi rəyini daha sürətli ölçür.

Etibarlı buraxılışlar

Avtomatik və təkrarlanan proseslər gündəlik tapşırıqları çox etibarlı hala gətirəcək və gündə mövcud olan bütün addımları aşağıdakı maddələr kimi avtomatlaşdırmağa çalışmaq məqsədi daşıyır:

  • Kod və kod keyfiyyətinin təhlilini tərtib etmək.
  • Vahid test, inteqrasiya testi, komponent testi, funksional test, sona qədər sınaq.
  • Ətraf mühitin təmin edilməsi, o cümlədən giriş mühiti, monitorinq və xəbərdarlıq ilə istehsal mühiti.
  • İstehsal da daxil olmaqla, bütün mühitlərə proqram təminatının yerləşdirilməsi.
  • Məlumat Mağazası miqrasiyası və verilənlər bazası mənbəyinə nəzarət.
  • Performans sınağı, təhlükəsizlik testi və nöqsanlara qarşı dözümlülük kimi qeyri-funksional test.
  • Bütün dəyişiklikləri izləmək və yoxlamaq.

Keçid Sol, təhlükəsizlik təminatını və ya uyğunluq qaydalarını təyin etmək xərclərini azaltmaq üçün, proqram təminatının ömrünün son dövrlərində normal olaraq görülən bütün vəzifələri mümkün qədər erkən gətirən prosesdir.

Bitmiş Təriflə görüş

Görülən tərifin kodlaşdırılması, kodunuz boru kəmərindən keçdikdə və bütün addımları keçdikdə, hər hansı digər əl yoxlama addımları olmadan istehsal üçün hazır olacağını təmin edəcəkdir, çünki kodunuz bütün qəbul meyarlarına və xəritələrə tam uyğun gəlir. iş tələbi.

Domain modelləşdirmə və İstifadəçi hekayələri Xəritəçəkmə, işin məhsulun nəyə bənzəməli olduğunu və müştəriyə çatdırılan dəyəri maksimum dərəcədə artırmaq üçün necə qurulmalı olduğunu anlamağa kömək edə bilər.

Davamlı çatdırılma üçün memarlıq dizaynı

Davamlı Çatdırılma ilə əldə etməyə çalışdığımızı daha yaxşı başa düşmək üçün, oyunda olan fərqli komponentləri görmək üçün Java proqramı üçün bir boru kəmərinə baxmağa başlayacağıq. Bu həm də boru kəmərinin arxitekturası və daha başlıcası tətbiqinizdə yaxşı bir memarlığın olması zəruriliyinin anlayışını verəcəkdir.

Yaxşı bir memarlıq üçün əsaslı bir anlayışa sahib olmaq, boş birləşdirilmiş komponentlər və yüksək birlik prinsiplərinə əməl edən sistem dizaynının vacibliyini və bu təcrübələrə həm iş, həm də texniki baxımdan əməl olunmadığı təqdirdə nə qədər xərcləndiyini görməyə kömək edə bilər.

Boş bağlama

Sistem içərisindəki komponentlərin boş bir şəkildə birləşməsi həmin komponentlərin gerçək icrası barədə heç bir məlumatın olmamasını və ya heç bir məlumatın olmamasını və bu komponentlər arasındakı qarşılıqlı əlaqənin bir konkret həyata keçirilməsinə etibar etməməsini təmin edir. Bunun əsas üstünlüyü ondan ibarətdir ki, komponentlər dəyişdirilə və ya tamamilə həyata keçirilə bilər və hələ də bir sistem səviyyəsində eyni funksionallıq təmin edir.

Java dünyasında boş bir əlaqə haqqında danışdıqda, bu iki yerdə görünə bilər, birincisi proqramlaşdırma dilində, ikincisi tətbiqetmə və ya xidmət səviyyəsindədir. Java proqramlaşdırma səviyyəsində boş boşluqlar haqqında danışarkən ümumiyyətlə onu kapsulasiya kimi şərh edirik. Bu ya da konkret sinif tipləri üzərindəki imza interfeyslərində və bir sinifin daxili vəziyyətinə daxil olmaq üçün giricilərdən və nizamlayıcılardan istifadə etməklə istifadə edilə bilər. Sinifin daxili vəziyyətinə nəzarət üçün yüksək inkişaf təmin ediləcəkdir. birbaşa digər komponentlərə təsir göstərir.

Xidmət səviyyəsində bir tətbiqdə, boş bir əlaqə adətən çevik və yaxşı qurulmuş bir quruluş təqdim edən komponent interfeysləri ilə əldə edilir. Komponentlər üçün boş boşluğa misal olaraq HTTPS üzərində JSON ilə Pakt və ya Bahar Bulud Müqaviləsi kimi REST müqavilələrindən istifadə olunur. GRPC, GraphQL və ya Apache Avro kimi bir interfeys tərifi dilini (IDL) istifadə edərək bunun üçün daha yaxşı qarşılaşılan bir üsul. Java tətbiqetməsində boş birləşmənin tam əksi, domen obyektlərinin yerli Java formatında mübadilə edildiyi Java RMI-dir.

Boş bağlama sistem elementləri arasında lazımsız qarşılıqlılığı minimuma endirsə də, bu cür qarşılıqlı əlaqə istədikdə problem yarada bilər. Məsələn, bəzi məlumat mərkəzli sistemlərdə real vaxt rejimində sinxronizasiya üçün yüksək dərəcədə element qarşılıqlı asılılığı lazımdır.

Yüksək birlik

Birləşmə, bir komponentin içərisindəki müxtəlif elementlərin bir-birinə aid olma səviyyəsini ifadə edən bir termindir. Java tətbiqi üçün fərqli bir iş parçasının ya bir sinifdə, nə də modulda bir-birinə nə dərəcədə uyğun olması səviyyəsində birliyimizi düşünə bilərik. Yüksək birliyi olan komponentlərə sahib olmağımızın səbəbi, ümumiyyətlə proqram üçün etibarlılıq, təkrar istifadə edilə bilmə, möhkəmlik və sistemin anlaşılması kimi çox arzu olunan xüsusiyyətlər toplusunu gətirməsidir. Java proqramlaşdırma səviyyəsində bir paketin gətirdiyi funksionallıq baxımından birlik haqqında düşünə bilərsiniz, əgər yeganə funksionallığı bir PDF faylı oxuduğunu düşünürsənsə, bu paketin yüksək birliyə sahib olduğunu söyləyə bilərsən, əksinə artıq aşağı səviyyəli birliyə sahib olan e-poçt xəbərdarlıqlarını göndərəcək bir paketə sahib idi.

Bu da tətbiq səviyyəsində vacibdir, burada xidmətlərin birləşməsi onların interfeysləri və məruz qaldıqları hərəkətlərlə müəyyən edilir. Beləliklə, əgər biz Kitab xidmətindən istifadə etsəydik, gözlədiyiniz hərəkətlərin hamısı onun modeli ilə əlaqəli olsaydı, bir kitabın detallarını yeniləyəcəksiniz, yeni bir kitab əlavə edin, bir kitab çıxarın və ya bir təqdimat tətbiq edəsinizsə yüksək birliyə sahib olacağını gözləyin. kitab. Digər tərəfdən, bu xidmət Yazar üçün də bir funksionallıq təmin edərsə, xidmətin aşağı birliyə sahib olduğunu söyləyərik.

Bulud doğma tətbiqi

Amazon Veb Xidmətləri, Google Bulud Platforması və Microsoft Azure-dən Heroku və Bulud Tökmə kimi xidmət təklifləri və ən yeni olanlar, xidmət təklifi olaraq konteyner olaraq son illərdə fərqli formalarda bulud hesablama təkliflərinin artması ilə var. Java tətbiqetmənizi necə yerləşdirmək və işə salmaq istədiyinizə dair daha çox seçimdir. Bu yeni geniş seçim proqramların qurulmasının buludlara hazır olduğunu və Xidmət quruluşu kimi bir platformada işləməsi üçün optimallaşdırılmasını təmin etmək üçün yeni memarlıq ən yaxşı təcrübələrinə ehtiyacı artırdı.

Java dəstəkləmə xidmətləri əsasən REST API-lər formasını aldı və dizayn baxımından, bütün istifadəçi tələblərinin yerinə yetirilməsini təmin etmək üçün dizaynın ən yaxşı yolu kənarda olan yanaşmaya əməl etməkdir. Bu, API-nin inkişaf etdiriləcəyi və daxili tətbiq detallarının interfeys vasitəsilə yayılmamasını təmin edən tərtibedici bir yanaşmadır. Bu yanaşma, həyata keçirməyə diqqət etmədən başlamazdan əvvəl əsasən interfeysi düzgün əldə etməyimizi və interfeys inkişaf qrupu tərəfindən deyil, istifadəçinin tələbləri ilə idarə olunmalı deməkdir, beləliklə BDD əsl dəyər mənbəyidir, dizayn bu xidməti həyata keçirməyə başlamazdan əvvəl bütün iş qəbul etmə meyarlarını nəzərə alacağıq. Bu məkanda yaxşı bir BDD texnikası Üç Amigosdur, burada tələblər bir tərtibatçı, bir test mütəxəssisi və bir iş nümayəndəsinin perspektivi ilə müəyyən edilir, çünki onlar fərqli suallar verəcək və bütün ehtiyacların ödəniləcəyini təmin edəcəklər. Bu boşluqda Java tətbiqetmələrinin BDD-yə kömək etməsi üçün, API testini təyin etməyə kömək etmək üçün qəbul testləri və Swagger yaratmaq üçün Xiyar, Jbehave və ya Spock kimi bir çox vasitə var.

Cloud Native tətbiqlərinin yeni dünyasında, ərizə yazmaq üçün Supersonic Subatomic Java Container Birinci çərçivəsi olan Quarkus'u qeyd etməmək ayıb olar. Bu çərçivə Java proqram üçün inanılmaz dərəcədə aşağı yükləmə vaxtı və inanılmaz aşağı yaddaş istifadəsi təmin edərək Kubernetes kimi konteyner mühitində işləməyiniz üçün Java tətbiqetmənizi optimallaşdırır. Siz Kubernetes və ya oxşar platformalarda işləyəcək yeni bir Java tətbiqetməsini hazırlayırsınızsa, Quarkus və GraalVM-lərə bir nəzər yetirdim.

Cloud-Native Tətbiqləri müzakirə edərkən 2012-ci ildə Heroku tərəfindən açıqlanan On iki Faktor təzahürü haqqında unuda bilmərik. Bu, Dev qruplarına Davamlı Çatdırılmaya yönəlmiş bir bulud hazır tətbiqi yaratmağa kömək etmək üçün müəyyən prinsiplər və təlimatlar raylarıdır.

Build avtomatlaşdırılması

Hamımızın bildiyimiz kimi, Java - tərtib edilmiş bir proqramlaşdırma dilidir, yəni Java dərslərimizi bayt koda yığdıqdan sonra onu JVM-lə işləyən hər hansı bir maşında işlədə bilərik. Bu, işləmə müddətini asanlaşdırır, çünki nəzəriyyədə tətbiqimizi işlətmək üçün yalnız JVM-ə sahib olmağımız lazımdır. Digər tərəfdən, kodu tərtib edərkən meydana gələn çox sayda hərəkətli hissə də var ki, bunlar da qurulma zamanı məlumdur. Jenkins və ya CircleCI kimi davamlı inteqrasiya serverlərindəki quruluşun asanlıqla yerinə yetirilməsinə imkan verən layihələrinizdə Maven və ya Gradle kimi ixtisaslaşdırılmış bir alət istifadə etmənin bir çox faydası var.

Bu üstünlüklərdən bəziləri asılılığı idarə etmək, qurulma müddətini sürətləndirmək üçün artan tərtib etmək, bir layihə daxilində birdən çox modul üzərində çox vəzifəni idarə etmək bacarığı, müxtəlif mühitləri təsvir etmək və bütün lazımi komponentləri avtomatik olaraq yaratmaq üçün profillərdən istifadə edə bilər.

Gördüyümüz kimi, qurma vasitəsi Java kodunun bytecode tərtibinə kömək etməkdən daha çox, həm də asılılığı qurmaq, xarici bağlantıları idarə etmək, bir layihənin çox modul hissəsini idarə etmək, inkişafı sürətləndirən plaginlərə sahib olmaqdır. artefaktı sərbəst buraxmaq və bir artefakt deposuna yükləmək imkanına malikdir.

Bir Bahar Çəkməsi tətbiqi üçün Gradle qurmaq skriptinə bir nümunəyə nəzər salmaqla, bir qurma vasitəsinin əhatə etdiyi fərqli cəhətləri nümayiş etdirə bilərik:

buildscript {depozitlər {mavenLocal () mavenCentral () gradlePluginPortal ()} bağlantılar {classpath "org.springframework.boot: spring-boot-gradle-plugin: 2.1.7"}}
plugins {id "java" id "maven" id "fikir" id "jacoco"}
sourceCompatibility = 1.8 targetCompatibility = 1.8 assert System.properties ["java.specification.version"] == "1.8" || "11" || "12"
plugin tətbiq edin: "org.springframework.boot"
if (project.hasProperty ("prod")) {tətbiq olunur: "gradle / profile_prod.gradle"} else {tətbiq olunur: "pillə / profil_dev.gradle"}
defaultTəkanlar "bootRun"
qrup = "com.module.example.application" versiya = "0.0.1-SNAPSHOT"
description = "Bizim nümunə tətbiqimiz Gradle ilə qurulur"
test {useJUnitPlatform () "** / * IT *", "** / * IntTest *", "** / * CucumberIT *" testLogging {hadisələr 'FAILED', 'SKIPPED'} xaric edin
report.html.enabled = saxta}
tapşırıq inteqrasiyaTest (növü: Test) {useJUnitPlatform () description = "İnteqrasiya testlərini icra edin." qrup = "doğrulama" "** / * IT *", "** / * IntTest *" xaric "" ** / * CucumberIT * "daxildir
testLogging {hadisələr 'FAILED', 'SKIPPED'}
report.html.enabled = saxta}
tapşırıq xiyarTest (növü: Test) {description = "Xiyar BDD testlərini icra edin." qrup = "doğrulama" "** / * CucumberIT *" daxildir
testLogging {hadisələr 'FAILED', 'SKIPPED'}
report.html.enabled = saxta}
yoxlayın
yoxlamaq.qiymətləndirmə inteqrasiyaTest
tapşırıq testReport (növü: TestReport) {targetDir = file ("$ buildDir / report / test") reportOn test}
tapşırıq inteqrasiyasıTestReport (növü: TestReport) {təyinDir = fayl ("$ buildDir / report / testlər") hesabatOnteqrasiyaTest}
tapşırıq xiyarTestReport (növü: TestReport) {təyinDir = faylı ("$ buildDir / report / testlər") hesabatOncodTest}
konfiqurasiyaları {Təqdim olunan müddətin həyata keçirilməsi.exclude modulu: "yaz-açılış-başlanğıc-tomcat"}
depolar {mavenLocal () mavenCentral () jcenter ()}
asılılıqlar {həyata keçirmə "org.springframework.boot: yaz-açılış-başlanğıc-veb"
həyata keçirmə "io.dropwizard.metrics: metrics-core" həyata keçirmə "io.micrometer: mikrometer-registry-prometheus" performance "net.logstash.logback: logstash-logback-encoder" performance "org.hibernate: hibernate-core" tətbiqetmə " org.hibernate: hibernate-entitymanager "həyata keçirmə" org.hibernate: hibernate-envers "performance" org.hibernate.validator: hibernate-validator "performance" org.springframework.boot: yaz-açılış-starter-logging "performance" org. springframework.boot: yaz-açılış-başlanğıc-aktuator "
həyata keçirmə "org.springframework.security:spring-security-web" həyata keçirmə "org.postgresql: postgresql" testImplementation "com.jayway.jsonpath: json-way" testImplementation "io.cumber: xiyar-junit" testImplementation ": io.cuc. xiyar-bahar "testImplementation" org.springframework.security :spring-security-test "testImplementation" org.springframework.boot: yaz-açılış-test "testImplementation" junit: junit "testImplementation" org.mockito: mockito-core "testImplementation" com.h2database: h2 "}

Bu nümunədə Gradle build script-in qurulma aləti, asılılığımızı və onların sırasını yükləmək istədiyimiz yerin təyin olunduğunu görə bilərik. Bundan sonra, qurmağa çalışdığımız sistemin tələblərimizə uyğun olub olmadığını yoxlayır, bu vəziyyətdə də qurmağı işə salmağa çalışdığımız maşında Java 8, 11 və ya 12 varsa. İstehsal üçün düzgün konfiqurasiya ilə qurulmağımızı təmin etmək üçün bayraqlarda verilən istifadə etmək istədiyimiz iki fərqli profili də müəyyənləşdiririk. Ayrıca kodu əhatə edən hesabatları təmin edən Jacoco plagin kimi faydalı xüsusiyyətlər vermək üçün fərqli plaginlər tətbiq edirik. Həm də test tapşırıqlarını hansı növdə aparmaq istədiyimizə əsasən müəyyənləşdiririk və nəhayət Jar-a daxil ediləcək tələb olunan asılılıqlarımızı müəyyənləşdiririk ki, tələb olunan asılılıqların bir müddətə olub olmadığını yoxlamalı deyilik.

İndi qabların geniş istifadəsi ilə biz də kifayət qədər tez dəyişməyə uyğunlaşa biləcəyimizi təmin etmək üçün qurma prosesinin konteyner görüntü hissəsini təyin etməliyik, Docker hər yerdədir və biz həm Java tətbiqetməmizin qurulması, həm də işləmə vaxtı üçün istifadə edə bilərik. . Budur, tətbiqin qurulması və tətbiqin işlənməsi üçün istifadə edilə bilən, lakin konteynerə daxil olan müxtəlif səviyyələrdə asılılıq olan Docker çox mərhələli bir quruluş:

FROM pillə: 5.6.0-jdk12 COPY tətbiqini qurmaq / usr / src / app RUN pillə - build-file = / usr / src / app / build.gradle təmiz qurmaq
FROM qəbul kopiyası: 12-jre-hotspot RUN addgroup -g 999 -S qrupu && \ adduser -u 999 -S tətbiqetməsi -G istifadəçi qrupu İstifadəçi COPY - from = build /usr/src/app/target/exampleApp-0.0.1- SNAPSHOT.jar /usr/app/exampleApp-0.0.1-SNAPSHOT.jar 8080 ENTRYPOINT ("java", "- jar", "/ usr / app / exampleApp-0.0.1-SNAPSHOT.jar") təqdim edir

Bu çox sayda vəzifəyə görə, kodu tərtib etməkdə kömək etməkdən əlavə, bir qurma bir Java layihəsi üçün zərərlidir və xüsusən onu inkişaf dövrünüzü sürətləndirmək və bağlantıları idarə etmək üçün eklentlərdən istifadə etmək üçün belə bir şəkildə qurmaq daha asandır. kodu əhatə edən hesabatlar, bütün bu vəzifələr Davamlı Çatdırılmada daha böyük hədəfə kömək edir.

Funksional Test

Proqramınızın funksional elementlərinin sınanması, proqramın təhvil verilməsini iş üçün dəyər verir, uzunmüddətli müddətdə asandır və gözlənildiyi kimi yerinə yetirir. Funksional test səhvlərin olmamasını və sistemin etibarlı və genişlənə biləcəyini, iş tələblərinə cavab verməsini və bəzi hallarda həll yolumuzun nə qədər səmərəli olduğunu düşünə bilərik.

Testi iki kateqoriyaya, funksional və işlək olmayan testlərə ayırsaq da, hələ də bizi geniş kateqoriyalara buraxır və yalnız indi funksional testə diqqət yetirmək tam olaraq nəyi və necə sınamaq lazımdır? Müxtəlif növ testlərin aspektlərini və onların əlaqəli xərclərini və dəyərlərini bu kitabla oxumağı tövsiyə etdiyimi yaxşı bilirəm: Çevik Test: Lisa Crispin və Janet Gregory tərəfindən Testers və Çevik Komandalar üçün (AddisonWesley) praktik bələdçi. Çevik bir quruluş və tərzdə sınaqlar zamanı bir çox detala girir. Əsasən o kitabdan indi nümayiş etdirmək istədiyim, test növlərinin və onların məqsədlərinin əla nümayəndəsi olan Çevik Test Quadrants:

İndi tətbiqetməmizdə istifadə etmək üçün həmin müxtəlif növ testləri qoymalıyıq və testlərin ən yaxşı birləşməsinin nədə olduğunu və onların harada aparılacağını görməliyik. Bunun səbəbi, test strategiyamızın Fasiləsiz Çatdırılma zehniyyətimizə uyğun olmasını istəyiriksə, vahid testlər, qəbul testləri və nisbətlərin sürətli ehtiyaca qarşı tətbiqi ilə bağlı məlumatları tarazlaşdıran uyğun nisbətə sahib olmalıdır. , deterministik rəy. Məsələn, test yeni məlumat vermirsə, qüsurlar aşkarlanmır və ya sınaq çox uzun çəkirsə, tədarük yavaş olacaq və xərclər əhəmiyyətli dərəcədə artacaq. Fasiləsiz Çatdırılmaya nail olmaq üçün Davamlı Sınaq da etməliyik, buna görə əsas avtomatlaşdırılmış testlərin vahid və qəbul testləri olduğu sınaq testinin sona çatması üçün az sayda avtomatlaşdırılmış sona çatması və yönləndirilmiş kəşfiyyat testinin ən yaxşı yanaşması olduğu bir test strategiyası lazımdır. Buna görə əksər insanlar sınaq piramidası ilə tanış olurlar:

Bu, test növlərinə daha çox diqqət yetirməklə və piramidanın altındakı daha çox sayda test yaratmaqla birbaşa yanaşmanızı göstərir və piramidanın digər səviyyələrinə qalxdıqca testlərin sayı kəskin şəkildə azaldılmalıdır. piramidanın yuxarısında bu testlərin aparılması tələb olunan xərclərin.

Diamond modeli adlanan yeni bir test modeli də var ki, burada əsas diqqət inteqrasiya test təbəqəsidir. Sistemin tərkib hissələrinin çox olduğu və fərdi komponentlərin heç birində o qədər alqoritmik kod olmadığı hallarda bu cür sistemin müxtəlif komponentləri arasında inteqrasiya tarixən mövcud olduğu hallarda daha uyğun bir yanaşmadır. Mürəkkəb sistemlərdə baş verən kəsilmələrin əsas səbəbi.

Vahid Test

Hər bir test strategiyasının əsası, vahid testin nə ilə başlandığını, bu günlərdə də çox yayılmağa başlamış, bir çoxunun vahid testinin nə olduğunu bildiyi və ən azı birinin yazıldığını söylədi. Bloq yazılarından tutmuş Test Sürətli İnkişaf (TDD) haqqında saysız-hesabsız kitablara qədər bir çox mənbələr var, çünki bu, möhkəm və etibarlı bir sistem yaratmaqda güclü bir yanaşma olduğunu sübut etdi.

Başlamaq və ümumiyyətlə sınamaq istədiyimiz bir vahidi necə təyin etdiyimizi düşünmək istərdim. Düşünmürəm ki, vahid testə yalnız bir həqiqi tərif və ya yanaşma var. Həm Sociable, həm də tək yanaşmalar vahid testin etibarlı təsviridir, burada bir yanaşma, Sociable bir çox Java sinifləri arasında yayılmışdır və həmin siniflərin funksiyasını ayrı bir vahid kimi təsnif edir. Digər tərəfdən, Solitar yanaşma yalnız bir java sinfi daxilində funksionallıq sınamaq və siniflə bütün xarici əlaqələri ləkələmək istərdi. Hansı yanaşmanızı izləmək istəməyiniz o qədər də vacib deyil, çünki əldə etmək istədiyimiz tətbiqimizi təhlükəsiz bir şəkildə layihəyə yeni funksionallıq əlavə etməyə kömək edə biləcək avtomatlaşdırılmış bir şəkildə sınamaqdır. JUnit Java-da test üçün ən çox istifadə olunan kitabxanalardan biridir və toplanışınızdakı əsas vasitədir, çünki orada olan digər kitabxanaların əksəriyyəti ilə əla işləyir. Bölmə testi üçün, başqa bir vasitəyə ehtiyac duyacağıq ki, sınaq üçün kiçik bir vahidi təcrid etmək çətin olsa, asılılığımızı ləkələmək və ya istehza etmək lazımdır. Bu faydalı vasitələrdən biri də Mockito, test kötükləri və ya lağlardan istifadə vahid sınaq üçün xarakterik deyil. Bunlar sisteminizin bütün hissələrini nəzarətli bir şəkildə simulyasiya etmək üçün istifadə edilə bilər, ancaq vahid testdə çox sayda istehza və kötük görməyiniz mümkündür, çünki çox sayda müasir dillər və kitabxanalar qurmağı asan və rahat hala gətirir. up istehza və stub.

Veb konfiqurasiya sinifimiz üçün bir neçə vahid testinin qısa bir nümunəsi, sınaqdan keçirməyə çalışırıq və tətbiqimiz üçün istədiyiniz konfiqurasiyanın olub olmadığını, bu testi CORS filtrinə yönəldəcəyik.

paket com.example.continuousdelivery.config;
idxal org.junit.jupiter.api.BeforeEach; idxal org.junit.jupiter.api.Test; idxal org.springframework.http.HttpHeaders; idxal org.springframework.mock.env.MockEn environment; idxal org.springframework.mock.web.MockServletContext; idxal org.springframework.test.web.servlet.MockMvc; idxal org.springframework.test.web.servlet.setup.MockMvcBuilders; idxal org.springframework.web.cors.CorsConfiguration;
idxal javax.servlet.Filter; idxal javax.servlet.FilterReg qeydiyyat; idxal javax.servlet.Servlet; idxal javax.servlet.ServletReg qeydiyyat;
idxal java.util.ArrayList; idxal java.util.Arrays; idxal java.util.Collections;
idxal statik org.mockito.ArgumentMatchers.any; idxal statik org.mockito.ArgumentMatchers.anyString; idxal statik org.mockito.Mockito. *; idxal statik org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; idxal statik org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; idxal statik org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; idxal statik org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
ictimai sinif WebConfigurerTest {
özəl WebConfiguration webConfiguration;
özəl MockServletContext servletContext;
özəl MockEn environment env;
özəl CorsConfiqurasiya konfiqurasiyası;
@BeforeEach ictimai boşluq quraşdırma () {servletContext = casus (yeni MockServletContext ()); doReturn (istehza (FilterRegistration.Dynamic.class)) .yaxın olduqda (servletContext) .addFilter (anyString (), any (Filter.class)); doReturn (istehza (ServletRegistration.Dynamic.class)) .qaydalananda (servletContext) .addServlet (anyString (), any (Servlet.class));
env = yeni MockEnvironment ();
webConfiguration = yeni WebConfiguration (env);
config = yeni CorsConfiguration (); config.setAllowedOrigins (Collections.singletonList ("*")); config.setAllowedMethods (Arrays.asList ("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders (Collections.singletonList ("*")); config.setMaxAge (1800L); config.setAllowCredentials (əsl); }
@ Ən çox açıq boşluq testCorsFilterOnApiPath () İstisna atır {MockMvc mockMvc = MockMvcBuilders.standaloneSetup (yeni WebConfigurationTestController ()) .addFilters (webConfiguration.corsFilter (config)).
mockMvc.perform (seçimlər ("/ api / test korsları"). başlıq (HttpHeaders.ORIGIN, "başqaları.domain.com"). başlıq (HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST")) .Ekspekt (status (). isOk ()) .Expect (başlıq (). string (HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "başqaları.domain.com")) .andExpect (başlıq (). string (HttpHeaders.VARY, "Mənşə")) .EndExpoT (başlıq (). string (HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "Get, POST, PUT, DELETE"). "));
mockMvc.perform (get ("/ api / test-cors"). başlıq (HttpHeaders.ORIGIN, "başqaları.domain.com")) .Endpect (status (). isOk ()) .andExpect (başlıq (). string (HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "başqaları.domain.com")); }
@ Ən çox açıq boşluq testiCorsFilterOnOtherPath () İstisna atır {
MockMvc mockMvc = MockMvcBuilders.standaloneSetup (yeni WebConfigurationTestController ()) .addFilters (webConfiguration.corsFilter (config)) .build ();
mockMvc.perform (get ("/ test / test-korslar"). başlıq (HttpHeaders.ORIGIN, "başqaları.domain.com")) .Endpekt (status (). isOk ()) .Expect (başlıq (). doNotExist (HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); }
@ Ən çox açıq boşluq testCorsFilterDeactived () İstisna {config.setAllowedOrigins (null) atır;
MockMvc mockMvc = MockMvcBuilders.standaloneSetup (yeni WebConfigurationTestController ()) .addFilters (webConfiguration.corsFilter (config)) .build ();
mockMvc.perform (get ("/ api / test-cors"). başlıq (HttpHeaders.ORIGIN, "başqaları.domain.com")) .Endpekt (status (). isOk ()) .Endpect (başlıq (). yoxdurNotExist (HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); }
@ Ən çox açıq boşluq testCorsFilterDeactived2 () İstisna {config.setAllowedOrigins (yeni ArrayList <> ()) istisna edir;
MockMvc mockMvc = MockMvcBuilders.standaloneSetup (yeni WebConfigurationTestController ()) .addFilters (webConfiguration.corsFilter (config)) .build ();
mockMvc.perform (get ("/ api / test-cors"). başlıq (HttpHeaders.ORIGIN, "başqaları.domain.com")) .Endekspekt (status (). isOk ()) .Expect (başlıq (). yoxdurNotExist (HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); }
}

Yuxarıda göstərilən vahid testində veb serverimizin korson konfiqurasiyasının gözlədiyimiz kimi işlədiyini və gələcəkdə WebConfiguration sinifinin tətbiqini dəyişdirdiyimiz təqdirdə bunu edə biləcəyimizi təmin etmək istəyirik. bu funksiyanı qaçırmırıq və testlər keçəcəkdir. Mockitonun tətbiqin qalan hissəsini lağ etmək üçün istifadə etdiyimizi görə bilərsiniz, çünki biz yalnız servletlə maraqlandıq. Bu bölmə testləri üçün testlərin əhatə dairəsini göstərmək üçün JaCoCo hesabatlarını da əlavə etdim ki, test etməyimiz lazım olanı görək və tətbiqimizin böyük bir hissəsini sınamadığımızı təmin edək.

İnteqrasiya Testi

Tətbiqinizin məntiqinin kiçik, təcrid olunmuş hissələrini sınamaq üçün əla bir üsul olan vahid testlərinə baxmağa başladıq, çünki onlar yazmaq üçün asandır və sürətlidirlər. Digər tərəfdən, bölmə testlərini yazarkən, ümumiyyətlə tətbiqetmənin daha yaxşı təcrid və daha sürətli sınaqlarla qarşılaşmağı tərk etdiyiniz hissələri var. Əksər tətbiqlərdə digər hissələrlə qarşılıqlı təsirlər mövcuddur və qarşılıqlı əlaqə sınanmalıdır. İnteqrasiya Testləri müxtəlif komponentlərin bu sınağına nail olmağınıza kömək etmək üçün burada.

Terminoloji baxımdan, komponent komponentlərinin tətbiqi daxilində və mənim nöqteyi-nəzərimdən test etdikdə, bir API haqqında danışdığımız zaman inteqrasiya testi istehlakçı tərəfindən idarə olunan müqavilələr testini də əhatə edir. fərqli API-lər arasındakı müqaviləni pozmadığınızı görmək. Buna əsaslanaraq tətbiqiniz üçün inteqrasiya testləri, tətbiqin bir hissəsi olan hər iki komponentin, eyni zamanda tətbiqinizdən kənarda yaşayanların da testlərin yaxşı əhatə olunmasını təmin edəcəkdir.

İnteqrasiya testlərini yazmağa kömək etmək üçün bir çox fərqli yanaşma var və mən prosesi sürətləndirmək üçün faydalı ola biləcək bir neçəsini nümayiş etdirəcəyəm. İstehsalda necə işlədiklərini və ya qarşılıqlı təsir göstərdiklərini test edərkən real komponentləri mümkün qədər yaxın istifadə edə biləcək bir test üsuluna sahib olmaq olduqca vacibdir. Ayrıca, inteqrasiya testini müzakirə edərkən, yalnız bir anda bir inteqrasiya nöqtəsini sınamaq istəyəcəyik və bu inteqrasiya nöqtəsinə cəmləşəcəyi üçün bu işin aşağı salınması olduqca vacibdir. mümkün olan səhvləri tapmaq üçün.

İnteqrasiya testlərini yazarkən mənim üçün əsas vasitələrdən biri TestContainers oldu, bu da verilənlər bazası, mesaj növbələri və ya veb serverlər və ya bağlantılar ilə asılılığı olan qısa müddətli bir test rejimində tətbiqinizi işə salmaq üçün istifadə edilə bilən Java kitabxanası oldu. bir Docker konteynerinin içərisində işləyə bilən bir şey, bu gün demək olar ki, hər şey.

paket com.example.continuousdelivery.repository;
idxal org.junit.Bundan əvvəl; idxal org.junit.Rule; idxal org.junit.Test; idxal org.springframework.data.redis.RedisSystemException; idxal org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; idxal org.springframework.data.redis.core.StringRedisTemplate; idxal org.testcontainers.containers.GenericContainer;
idxal statik org.assertj.core.api.Assertions.assertThat;
ictimai sinif UserRepositoryTest {
final String id = "testcontainers";
UserRepository depo;
@ Qayda açıq GenericContainer redis = yeni GenericContainer ("redis: 3-alp") .EksposedPort (6379);
@ Əvvəlcədən açıq boşluq setUp () {Kahı BağlantıFabrik bağlantısıFabriki = yeni KahıKəsməFabriki (redis.getContainerIpAddress (), redis.getFirstMappedPort ()); əlaqəFactory.afterPropertiesSet (); depo = yeni UserRepository (yeni StringRedisTemplate (connectionFactory)); }
@Test ictimai boşluq testEmptyIfNoKey () {assertThat (repository.findAll (id)). İsEmpty (); }
@ Test (gözlənilən = RedisSystemException.class) ictimai boşluq testLimits () İstisna {repository.redisTemplate.opsForHash () .put (repository.toKey (id), "5", Long.MAX_VALUE + "") atır;
depo.add (id, 5); }}

Yuxarıdakı nümunədə Redis Docker konteynerini fırlatmaq və testimizi bu misala qarşı aparmaq üçün TestContainers kitabxanasından istifadə edirik ki, bu da tətbiqimizlə Redis nümunəsi arasındakı dəqiq qarşılıqlı əlaqəni təkrarlayaraq Redis nümunəsinə istehza ilə müqayisədə daha dəqiq olar. .

İnteqrasiya testləri apararkən əlimizdə olan REST API-lərini yaratmaq üçün digər vacib bir vasitə, istifadəçilərinizin gözləntilərinə uyğun bir xidmət yaratmağınıza və pozmamağınıza əmin olmaq üçün olduqca vacib olan İstehlakçı İdarəetmə Müqavilələridir. iki xidmət arasında bir inteqrasiya nöqtəsini qıraraq daha böyük sistem. Bu gün ayrı-ayrı qruplar var ki, bir-birinin ayaq barmaqlarına basmadan fərdi və boş birləşdirilmiş xidmətləri qurur və bu xidmətləri, yazının əvvəlində danışdığımız kimi, böyük, vahid bir sistemə birləşdirir. Mikroservislərdəki son diqqət və həyəcan məhz budur.

İdeal olaraq, istehlakçıdakı müştəri komponenti və ya provayderdəki interfeys komponenti dəyişdirildikdə müqavilə sınaqlarını həyata keçirəcəksiniz. Ancaq provayder müqavilənin təsdiqlənməsində iştirak etmirsə, provayderin nə vaxt dəyişdiyini bilmirsiniz. Müştəri komponentiniz dəyişdikdə müqavilə testlərini yerinə yetirmək üçün boru kəmərinizi konfiqurasiya etməlisiniz və sonra müştəri komponentinizi nə qədər dəyişdiyinizdən asılı olaraq nizamlı bir test icrasını (məsələn, gündəlik və ya həftəlik) provayder tərəfində baş verə biləcək hər hansı bir dəyişiklik aşkar etməyə çalışın.

Hər RESTful qarşılıqlı əlaqə üçün iki iştirakçı var: provayder və istehlakçı. Provayder, istehlakçılara məlumat verir. İstehlakçı bir provayderdən əldə edilən məlumatları emal edir. Bir REST dünyasında bir provayder, tələb olunan son nöqtələri olan bir REST API qurur; bir istehlakçı məlumat almaq və ya digər xidmətdə dəyişiklik başlatmaq üçün bu REST API-ə zənglər edir. Asenkron, hadisələrin idarə olunduğu bir dünyada bir provayder (bir qayda olaraq naşir adlanır) məlumatları bir növbəyə yayımlayır; bir istehlakçı (çox vaxt abunəçi deyilir) bu növbələrə abunə olur və məlumatları oxuyur və işləyir. Yoxsa RESTR API interfeysinizi təyin etmək üçün GraphQL və ya gRPC-yə baxa bilərsiniz və vanil JSON ilə müqayisədə bu iki fərqli yanaşma vasitəsi ilə xidmətinizi ifşa edə bilərsiniz, çünki o şəxslər məruz qalan modelin tərifini almağa məcbur edir və müqavilənin sınaq aspektini asanlaşdırır.

Bu səbəbdən müqavilə testləri tipik test alətlərindən istifadə edilmədən yazılmalı, ancaq ixtisaslaşmış olanlardır. Müqavilənin özü həm provayderlər, həm də istehlakçılar tərəfindən abunə olduqları müstəqil bir fiziki şəxs tərəfindən təmsil olunmalıdır: provayder müqavilənin istehsal etdikləri məhsulların ədalətli təmsilçiliyini təsdiqləyir, istehlakçılar isə göstərdikləri cavab təminatçının cavablarının öhdəsindən gələ bildiklərini təsdiqləyirlər. müqavilə. API-də bir dəyişiklik tələb olunduqda, müqavilə yenilənir və hamı testlərini yenilənmiş versiyaya qarşı təkrar edir.

JSON'u ifşa edən REST API-i sınamaq üçün yaxşı bir vasitə əlinizdə olan əla bir vasitədir və birbaşa istifadəçilərinizdən gələn tələblərə əsaslanaraq API qurmaq və inkişaf etdirmək üçün yaxşıdır. Həqiqi provayderi lağ etmək və PACT DSL inyeksiya etmək və PACT ilə təyin olunan müqaviləyə qarşı test etmək üçün PACT kitabxanalarının köməyi ilə yazılmış bir testin sürətli bir nümunəsini sizə göstərəcəyəm, bu şəkildə həm provayderin, həm də istehlakçının sapmamasını təmin edə bilərik xəbərdarlıq etmədən modeldən.

paket com.example.continuousdelivery;
idxal au.com.dius.pact.consumer.Pact; idxal au.com.dius.pact.consumer.PactProviderRuleMk2; idxal au.com.dius.pact.consumer.PactVerification; idxal au.com.dius.pact.consumer.dsl.PactDslWithProvider; idxal au.com.dius.pact.model.RequestResponsePact; idxal com.example.continuousdelivery.domain.User; idxal com.example.continuousdelivery.service.UserService; idxal org.junit.Rule; idxal org.junit.Test; idxal org.junit.runner.RunWith; idxal org.springframework.beans.factory.annotation.Autowired; idxal org.springframework.boot.test.context.SpringBootTest; idxal org.springframework.test.context.junit4.SpringRunner;
idxal java.util.HashMap; idxal java.util.Map;
idxal statik org.junit.Assert.assertEquals;
@RunWith (SpringRunner.class) @SpringBootTest ictimai sinif PactJunitRuleTest {
@Hazırlanmış İstifadəçiServis istifadəçisiService;
@ Qayda açıq PactProviderRuleMk2 mockProvider = yeni PactProviderRuleMk2 ("ExampleProvider", bu);
@ Pakt (istehlakçı = "JunitRuleConsumer") ümumi İstəkResponsePact createPact (PactDslWithProvider builder) {Xəritə başlıqlar = yeni HashMap (); headers.put ("Məzmun növü", "tətbiq / json; charset = UTF-8");
qayıtma qurucusu .given ("") .uponReceiving ("Misal Pakt qarşılıqlı əlaqəsi") .path ("/ api / users / admin") .metod ("Get") .willR muxbir (başlıqlar) .status (200) .body ("{\ n" + "\" id \ ": 3, \ n" + "\" giriş \ ": \" admin \ ", \ n" + "\" ilk adı \ ": \" İdarəçi \ ", \ n" + "\" son ad \ ": \" İdarəçi \ ", \ n" + "\" e-poçt \ ": \" admin @ localhost \ ", \ n" + "\" imageUrl \ ": \ "\", \ n "+" \ "aktivləşdirildi \": doğrudur, \ n "+" \ "langKey \": \ "en \", \ n "+" \ "Yaradıldı \": \ "Sistem \" , \ n "+" \ "YaradılmışDate \": null, \ n "+" \ "sonModifiedBy \": \ "sistem \", \ n "+" \ "lastModifiedDate \": null, \ n "+" \ "səlahiyyətləri \": [\ n "+" \ "ROLE_USER \", \ n "+" \ "ROLE_ADMIN \", \ n "+"] \ n "+"} ") .toPact (); }
@ Test @PactVerification ictimai boşluq runTest () {userService.setBackendURL (mockProvider.getUrl ()); İstifadəçi istifadəçi = userService.getUser (); assertEquals (user.getLogin (), "admin"); }}

Yuxarıdakı testdə Provayderi lağa qoyuruq və müqavilənin tərifinə əsaslanaraq istifadəçini bizə qaytarmaq üçün PACT istifadə edirik. Beləliklə, istehlakçımızın düzgün tətbiq olunduğunu yoxlamaq üçün yoxladığımız zaman, razılaşdırılmış müqaviləyə qarşı təsdiqlənəcəkdir.

Qəbul Testi

İndi testin növbəti səviyyəsinə və ya sınaq növünə, qəbul testinə keçə bilərik, bu sistemin buraxılış üçün məqbul bir vəziyyətdə olub olmadığını yoxlamaq üçün sınaqdan keçirildiyi proqram səviyyəsidir. Bu tip testin məqsədi sistemin iş tələblərinə uyğunluğunu qiymətləndirmək və çatdırılma üçün məqbul olub olmadığını təsdiq etməkdir.

Qəbul testləri, hər hansı digər testlər kimi, testləri və sonda bir neçə mülahizəni işə salmadan əvvəl bəzi quraşdırma ilə avtomatlaşdırılmış hərəkətlər toplusundan ibarətdir. Qəbul testləri müxtəlif səviyyələrdə ola bilər. Çox vaxt onlar olduqca yüksək səviyyədədirlər və xidmətinizi istifadəçi interfeysi vasitəsi ilə sınayacaqlar və beləliklə onlara bir az fərqli münasibət göstərməyə imkan verir.

Qəbul testi ilə, test məlumatlarını yaratmaq və silməkdən başlayaraq, xidmətlərin yaradılması və məhv edilməsini necə idarə etdiyimizə qədər yoxlaya biləcəyimizə qədər hər şeyin tam nəzarətimizdə olduğu avtomatlaşdırılmış testin ən yüksək formasına nail ola bilərik. daxili vəziyyətin bütün təfərrüatları. Bu, qəbul testlərinin yeni tələb forması kimi qəbul testlərinin və nəticələrinin qeyri-texniki insanlar tərəfindən asanlıqla oxunub başa düşülə biləcəyi səviyyəyə qədər yeni bir yol açır.

Ümumiyyətlə, Qəbul Testi haqqında danışmağa başladıqda, proqram təminatını istifadəçilərin ehtiyaclarına və diqqətinə yönəltməli olan bir ssenari formatında qəbul testlərini yazmaq üçün TDD kimi bir üsul olan Behaviz İdarəetmə İnkişafı (BDD) haqqında da danışmağa başlayırıq. tələblər. Daha dəqiq işlədikləri üsul, hər istifadə ssenarisi üçün bir istifadəçinin sistemdən istifadə edərkən qəbul edilməli olduğu davranış-tələbat var, buna görə də bu davranışı addımlar toplusu olaraq ifadə edirik və sonra bu addımları çeviririk icra olunan tədbirlər. Bundan sonra icra olunan hərəkətlər sistemə qarşı tətbiq edilə bilər və hamısı uğur qazansa, ssenari uğurla həyata keçirildiyi hesab edilə bilər.

Testə başlamazdan əvvəl sistemlərin vəziyyətini ifadə edən Verilmişdən başlayaraq BDD testlərini təyin etdikdə və yazdığımızda üç əsas addım var, bu test etmək istədiyimizi təsvir edir və tələb olunan dəsti təmin etməyimiz üçün kontekstlidir. sınamadan əvvəl xüsusi bir ssenari üçün hazırladıq. İkinci addım, bunların çoxuna sahib ola biləcəyimiz və istifadəçinin sistemə qarşı edəcəyi hərəkətləri göstərir. Sonuncu addım Sonra və sistemə qarşı edilən əvvəlki hərəkətlərin nəticələrini göstərir və nəticənin gözləntilərini sistemlərin gerçək şəkildə geri qayıtdıqları ilə müqayisə etdiyimiz yerdə sınanma nöqtəmizi ifadə edir.

BDD yanaşması ilə Java-da qəbul testlərini yazmaq üçün ən çox istifadə olunan çərçivələrdən biri, qeyri-texniki insanlara, insanlarda təsvir edilmiş əvvəlki 3 addımdan ibarət olan çərçivə terminologiyasında bilinən ssenariləri və ya xüsusiyyətləri yazmağa imkan verən xiyardır. - başa düşülən format, məsələn burada bir xüsusiyyət var:

Xüsusiyyət: İstifadəçi idarəetməsi
    Ssenari: İstifadəçi axtardığım zaman idarəçi istifadəçisi alın 'Sonra istifadəçi tapılır və soyadı' İdarəçi '

Bu xüsusiyyət doldurma Java sistemi tərəfindən test olaraq istifadə edilə bilməz, buna görə o İngilis cümlələrini sistem tərəfindən başa düşülən əmrlərə köçürmək üçün bir təbəqə yaratmalıyıq, fərqli təsvir etmək üçün istifadə oluna bilən bir siniflə başlayırıq. addımlar:

paket com.example.continuousdelivery.cucumber.stepdefs;
idxal org.springframework.test.web.servlet.ResultActions;
ictimai mücərrəd sinif StepDefs {
   qorunan ResultActions hərəkətləri;
}

İndi istifadəçi addımlarımız üçün sinif aşağıdakı kimi görünəcəkdir:

paket com.example.continuousdelivery.cucumber.stepdefs;
idxal com.example.continuousdelivery.controller.UserResource; idxal io.cucumber.java.Bundan əvvəl; idxal io.cucumber.java.en.Then; idxal io.cucumber.java.en.When; idxal org.springframework.beans.factory.annotation.Autowired; idxal org.springframework.http.MediaType; idxal org.springframework.test.web.servlet.MockMvc; idxal org.springframework.test.web.servlet.setup.MockMvcBuilders;
idxal statik org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; idxal statik org.springframework.test.web.servlet.result.MockMvcResultMatchers. *;
ictimai sinif UserStepDefs StepDefs'i genişləndirir {
   @ Xüsusi Şəxsi İstifadəçiResource userResource;
   özəl MockMvc mockMvc;
@ Əvvəlcədən açıq boşluq quraşdırma () {this.mockMvc = MockMvcBuilders.standaloneSetup (userResource) .build (); }
@When ("Mən istifadəçi axtarıram {string}") ictimai boşluq i_search_user (String userId) atıla bilən {hərəkətlər = mockMvc.perform (get ("/ api / users /" + userId) .qəbul (MediaType.APPLICATION_JSON)); }
@ Sonra ("istifadəçi tapıldı") açıq boşluq the_user_is_found () Throwable {hərəkətlərini atır. VəExpect (status (). İsOk ()) .andExpect (content (). ContentType (MediaType.APPLICATION_JSON_UTF8_VALUE)); }
@ Sonra ("onun soyadı {string}") açıq boşluq his_last_name_is (String lastName) atıla bilən {əməliyyatlar.andExpect (jsonPath ("$. LastName"). Value (lastName)); }
}

Bununla Java sistemimiz tərəfindən başa düşülə bilən təkrar istifadə edilə bilən addımlar yarada bilərik və hazırda son addım Gherkin formatında yazılmış bu xüsusiyyət faylı işlədən bir sinif yaratmaqdır.

paket com.example.continuousdelivery.cucumber;
idxal io.cucumber.junit.CucumberOptions; idxal io.cucumber.junit.Cucumber; idxal org.junit.runner.RunWith;
@RunWith (Xiyar.class) @CucumberOptions (plugin = "olduqca", xüsusiyyətləri = "src / test / xüsusiyyətləri") ictimai sinif CucumberIT {
}

Burada yalnız xüsusiyyət sənədlərini tapa biləcəyi və sınaqları keçirə biləcəyi Xiyar çərçivəsini göstəririk. Bu sayədə tətbiqinizin istifadəçilər tərəfindən qoyulmuş tələblərə uyğun olduğunu və onları doğruladığını təsdiqləmək üçün çox asan bir yola sahib ola bilərsiniz.

Sınaqdan sona qədər sınaq

Sınaqdan sona qədər sınaq, məhsulunuzun gözlənildiyi kimi davranmasını təmin etmək üçün proqram təminatınızın işləmədiyini və ya olmadığını qərar vermək üçün lazım olan etimadı təmin etmək üçün mümkün olan ən yüksək qiymətləndirmə formasıdır. Bu cür testlər ümumiyyətlə İstifadəçi İnterfeysindən başlanır və çoxsaylı mikroservisləriniz arasındakı bütün rabitə və əlaqələrin xidmətlər arasındakı boşluqları əhatə etməsini təmin etmək üçün bütün xidmətlərinizi əhatə edir. Daha əvvəl qeyd edildiyi kimi UI ilə başlayan testlər sona çatır, buna görə bir boru kəmərində buna kömək etmək üçün klik edə, məlumat daxil edə və istifadəçi interfeysinizi quraşdırılmış xidmətlərinizə görə yoxlaya bilən başsız bir brauzer lazımdır. Bir brauzer təqlid etmək üçün çox məşhur bir sürücü WebDriver protokolundan istifadə edən Seleniumdur.

Testlərin sonuna qədər mükəmməl deyil və onların tipləri yoxdur, çünki əksər tətbiqlərdə səhv və tez-tez gözlənilməz və səbəblərə görə uğursuz olur, yoxlanılan əsas xidmətlər düzgün qurulmadığı üçün ya da digərləri var ortada şəbəkə problemləri, yəni test mühiti istehsal mühitinizin əsl nüsxəsi deyildir. Sisteminiz böyüdükcə və daha çox mürəkkəbləşdikdə, birdən çox komponentdən ibarət olduqda, sona çatma sonu daha qeyri-sabit olmağa başlayır, çünki fərqli reaksiya göstərən müxtəlif brauzerlərlə, vaxt məsələlərinə, animasiyalara və düzəltmək üçün uzun müddət davam edə biləcək gözlənilməz açılan dialoqlara rast gələ bilərsiniz. və qorumaq.

Testin sonu sona çatması, xüsusən son bir neçə ildə qəbul edilən artan mikroservislər arxitekturası ilə xidmətlər arasında ötürülən mesajların düzgünlüyünü qiymətləndirmək üçün əsas məqsəddir, eyni zamanda firewall, proxy və ya yükləmə kimi əlavə şəbəkə infrastrukturunu təmin edir. tarazlayıcılar düzgün qurulmuşdur. Bu testlər eyni zamanda bir mikroservis arxitekturasının inkişafına imkan verir: Problem sahəsi haqqında daha çox məlumat əldə edildikdə, xidmətlərin parçalanma və ya birləşmə ehtimalı var və eyni funksiyanı təmin edəcəyinizlə testiniz eyni qalacaq.

Sınaq çərçivələrini və kitabxanaları sona çatdırmaq üçün sona baxmağa başladıqda, əksəriyyəti bu alətlər tətbiqinizi sınamaq asanlaşdırdığından test qurmağı asanlaşdıran Yaqut və ya JavaScript-ə əsaslanır. Java hələ də bir çox şirkət üçün istifadə edildiyi üçün, Java əsaslı bir həll yaratmaq, dünyada çox sayda komandaya kömək etmək üçün cavab ola bilər. Nümunələrin və texnikanın istifadəsi daha möhkəm, yaxşı quruluşlu və asan saxlanılması üçün sondan sona çatan testlərə imkan verir. Lakin bu nümunələrin tətbiqi də baha başa gəlir, buna görə flue2ent kitabxanası onu sadələşdirmək üçün bu vasitələrdən biridir. DSL (Domain Xüsusi Dili) konsepsiyasını BDD-dən gətirərək, kodun özünü təbii dilə yaxınlaşdırmaq üçün testləri bitirmək üçün Java sonumuz üçün flue2ent kitabxanasını nümayiş etdirməyi seçdim.

Java-da testlərin sona çatması üçün sonu yazmağa necə başlaya biləcəyinizi görmək üçün onların GitHub repolarından nümunələrdən birini göstərəcəyəm:

paket org.definitylabs.flue2ent.test;
idxal org.definitylabs.flue2ent.Website; idxal org.junit.Bundan əvvəl; idxal org.junit.Rule; idxal org.junit.Test; idxal org.junit.jupiter.api.DynamicTest; idxal org.junit.rules.ExpectedException; idxal org.junit.runner.RunWith; idxal org.mockito.Mock; idxal org.mockito.runners.MockitoJUnitRunner; idxal org.mockito.stubbing.Answer;
idxal java.util.ArrayList; idxal java.util.List; idxal java.util.stream.Stream;
idxal statik org.assertj.core.api.Assertions.assertThat; idxal statik org.mockito.Mockito. *;
@RunWith (MockitoJUnitRunner.class) ictimai sinif ScenarioTest {
    @ Şəxsi AbstractStep stepOne;
    @ Şəxsi AbstractStep stepTwo;
    Şəxsi AbstractStep addım atın Üç;
    @ Qayda açıq ExpectedException gözlənilirException = ExpectedException.none ();
    Şəxsi İnternet veb saytı;
şəxsi siyahısı addımlarOrder; Şəxsi AbstractDataStep-i istehza edin addımFour;
    @ Əvvəl ictimai boşluqdan əvvəl () {addımOrder = yeni ArrayList <> ();
Cavab cavabı = invokasiyaOnMock -> {addımOrder.add ((Addım) invocationOnMock.getMock ()); geri qayıtmaq; };
doAnswer (cavab) .səs (stepOne) .execute (); doAnswer (cavab) .səs (addım iki) .execute (); doAnswer (cavab) .yaxın (üçüncü) .execute (); }
    @Test public boşluğunu icra edinAt_given_executesStepsInSameOrder () {Ssenari ssenarisi = Scenario.title ("Başlıq"). Verilmişdir (stepOne) .Hərdə (addım İkiqat) .Hərdə (addım Üç) .qurulmaq ();
        ssenari.executeAt (veb sayt);
doğrulayın (stepOne) .execute (); doğrulayın (stepTwo) .execute (); doğrulayın (stepThree) .execute ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); təsdiqləyin (addımlarOrder) .QoşulmalarSərabət (stepOne, stepTwo, stepThree); }
    @Test public boşluğunu icra edinAt_whenGiven_executesStepsInSameOrder () {Ssenari ssenarisi = Ssenari.title ("Başlıq"). Nə zaman (addım-iki) .given (stepOne) .qədər (addım Üç) .build ();
        ssenari.executeAt (veb sayt);
doğrulayın (stepOne) .execute (); doğrulayın (stepTwo) .execute (); doğrulayın (stepThree) .execute ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); təsdiqləyin (addımlarOrder) .QoşulmalarSərabət (stepOne, stepTwo, stepThree); }
    @Test ictimai boşluğunu icra edinAt_when_executesStepsInSameOrder () {Ssenari ssenarisi = Scenario.title ("Başlıq"). Zaman (stepOne) .tən (addım Üç) .build ();
        ssenari.executeAt (veb sayt);
doğrulayın (stepOne) .execute (); doğrulayın (stepThree) .execute ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); təsdiqləyin (addımlarOrder) .güzəltmələr (stepOne, stepThree); }
    @Test ictimai boşluğunu icra edinAt_and_executesStepsInSameOrder () {Ssenari ssenarisi = Ssenari.title ("Başlıq"). Verilmişdir (stepOne) .Və (addımOne) .Həmdə (addım İkiqat) .Və (addım Üçüncü) .Hələlik (Üçüncü) .Və (addımDörd). qurmaq ();
        ssenari.executeAt (veb sayt);
doğrulayın (stepOne, dəfə (2)). icra edin (); doğrulayın (addım iki dəfə, dəfə (2)). icra edin (); doğrulayın (addım üçdə, dəfə (2)). icra edin ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); təsdiqləyin (addımlarOrder) .qoşluqlarSərabət (stepOne, stepOne, stepTwo, stepThree, stepThree, stepTwo); }
    @Test ictimai boşluğunu icra edinAt_withData_whenStepIsNotDataStep_callsExecute () {Ssenari ssenarisi = Scenario.title ("Başlıq"). Verilmişdir (stepOne) .build ();
Obyekt məlumatları = yeni Object (); ssenari.executeAt (veb sayt, məlumat);
        doğrulayın (stepOne) .execute ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); }
    @Test ictimai boşluğunu icra edinAt_withData_whenStepIsDataStep_callsExecute () {Ssenari ssenarisi = Ssenari.title ("Başlıq"). Verilmişdir (addımFour) .build ();
Obyekt məlumatları = yeni Object (); ssenari.executeAt (veb sayt, məlumat);
        doğrulayın (stepFour) .execute ();
assertThat (ssenari.getTitle ()). isEqualTo ("Başlıq"); təsdiq (addımFour.data ()) .SameAs (data); }
@ Test ictimai boşluq test_returnsDynamicTest () atıla bilən atır {Ssenari ssenarisi = Ssenari.title ("Sərlövhə"). Verildi (stepOne) .build (); DynamicTest testi = ssenari.test (veb sayt);
        assertThat (test.getDisplayName ()). isEqualTo (ssenari.getTitle ());
        test.getExecutable (). icra etmək ();
doğrulayın (stepOne) .execute (); }
@ Test public boşluğu test_withData_returnsDynamicTest () atıla bilən {Ssenari ssenarisi = Scenario.title ("Başlıq [{data.name}]") atıldı. Verildi (addımFour) .Sonra (stepOne) .build (); DataObject dataOne = yeni DataObject ("dataOne"); DataObject dataTwo = yeni DataObject ("dataTwo"); Siyahı testlər = ssenari.test (veb sayt, Stream.of (dataOne, dataTwo));
təsdiqləyin (testlər) .hasSize (2); assertThat (test.get (0) .getDisplayName ()). isEqualTo ("Başlıq [dataOne]"); assertThat (testlər.get (1) .getDisplayName ()). isEqualTo ("Başlıq [dataTwo]");
test.get (0) .getExecutable (). icra edin (); doğrulayın (stepFour) .execute (); doğrulayın (stepOne) .execute (); təsdiq (addımFour.data ()). isSameAs (dataOne);
test.get (1) .getExecutable (). icra etmək (); doğrulayın (addımFour, dəfə (2)). icra edin (); doğrulayın (stepOne, dəfə (2)). icra edin (); təsdiq et (addımFour.data ()) .SameAs (dataTwo); }
@Test ictimai boşluq test_withData_withoutVariable_returnsDynamicTest () atıla bilən atılır {Ssenari ssenarisi = Ssenari ssenari = Ssenari.title ("Başlıq"). Verilmişdir (addımFour) .tən (addımOne) .build (); DataObject dataOne = yeni DataObject ("dataOne"); DataObject dataTwo = yeni DataObject ("dataTwo"); Siyahı testlər = ssenari.test (veb sayt, Stream.of (dataOne, dataTwo));
təsdiqləyin (testlər) .hasSize (2); assertThat (testlər.get (0) .getDisplayName ()) .EEqualTo ("Başlıq"); assertThat (testlər.get (1) .getDisplayName ()) .EEqualTo ("Başlıq"); }
@Test ictimai boşluq testi_withData_whenPropertyDoesNotExists_throwsRuntimeException () {Ssenari ssenarisi = Scenario.title ("Başlıq [{data.wrongProperty}]"). Sonra (stepOne) .build (); DataObject dataOne = yeni DataObject ("dataOne");
gözlənilənException.expect (RuntimeException.class); gözlənilənException.expectMessage ("Verilənlərdən əmlak almaq xətası: data.wrongProperty");
ssenari.test (veb sayt, Stream.of (dataOne)); }
    ictimai statik sinif DataObject {xüsusi final String adı;
xüsusi DataObject (Sətir adı) {this.name = name; }
public String getName () {qayıtmaq adı; }}
}

Fəaliyyət testinin son mərhələsi, istehsal sisteminə qarşı real əməliyyatlar olan, lakin CI serveriniz tərəfindən idarə olunan bir test paketi kimi saxta istifadəçilər tərəfindən hazırlanan sintetik əməliyyatlar deyilən sona qədər sona çatan xüsusi bir növüdür. Testlərin istehsal sisteminə qarşı aparıldığı üçün bu testin ən yüksək mərhələsidir və eyni zamanda istehsalın təkrarlanması üçün nəzərdə tutulmuş bir mühitlə müqayisədə daha çox məlumat verəcəkdir, infrastrukturun və şəbəkənin monitorinqi kimi, ətrafınızın müəyyən bir şəkildə necə reaksiya verəcəyini görmək üçün. ssenarilər.

Recap

Bu yazı boyunca müzakirə etdiyimiz çox şey üçün nümunələr olan Java tətbiqetməsinin Fasiləsiz Çatdırılmasına nail olmaq üçün bəzi əsas məqamları əhatə etdik. Bu birinci hissədə Memarlıq Dizaynı, Quruluşun Avtomatlaşdırılması və Funksional Testin bütün aspektlərini əhatə etdik, ikinci hissə qarşıdakı həftələrdə izlənəcək və tələb olunan bütün işləri başa çatdırmaq üçün geniş qeyri-funksional sınaq və istehsal müşahidə qabiliyyətlərini əhatə edəcəkdir. Java tətbiqləri üçün tam davamlı Çatdırılma boru kəməri üçün ədəd. Bu yazıda göstərilən kod parçalarının çoxu ilə bir repo yaratmışam, burada görə biləcəyiniz hər şeyin necə birləşdiriləcəyi barədə daha yaxşı bir fikir vermək üçün.