{"id":20201,"date":"2023-03-27T05:00:00","date_gmt":"2023-03-27T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=20201"},"modified":"2023-03-22T10:02:03","modified_gmt":"2023-03-22T09:02:03","slug":"testcontainers-docker-w-pracy-testera","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/testcontainers-docker-w-pracy-testera\/","title":{"rendered":"Testcontainers \u2013 Docker w pracy testera"},"content":{"rendered":"\n<p>Spo\u015br\u00f3d wielu narz\u0119dzi, kt\u00f3re s\u0105 ju\u017c dost\u0119pne i tych, ci\u0105gle pojawiaj\u0105cych si\u0119 w dynamicznym \u015bwiecie IT, Testcontainers zwr\u00f3ci\u0142o moj\u0105 szczeg\u00f3ln\u0105 uwag\u0119 w minionym ju\u017c 2022 roku.<\/p>\n\n\n\n<p>Chcia\u0142bym przedstawi\u0107 Ci, jakie wyzwania i problemy mo\u017cemy rozwi\u0105za\u0107, dodaj\u0105c do swojego warsztatu t\u0119 opcj\u0119. Nie b\u0119dzie to artyku\u0142 wy\u0142\u0105cznie zachwalaj\u0105cy narz\u0119dzie, poniewa\u017c \u2013 poza jego oczywistymi zaletami \u2013 chc\u0119 r\u00f3wnie\u017c zwr\u00f3ci\u0107 uwag\u0119 na jego wady. Uprzedz\u0119 jednak m\u00f3j werdykt ko\u0144cowy \u2013 <strong>jestem zwolennikiem Testcontainers<\/strong> i ju\u017c na dobre ulokowa\u0142em je w moich frameworkach testowych.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/9-2.png\"><img decoding=\"async\" width=\"862\" height=\"350\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/9-2.png\" alt=\"Testcontainers w pracy testera\" class=\"wp-image-20224\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/9-2.png 862w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/9-2-300x122.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/9-2-768x312.png 768w\" sizes=\"(max-width: 862px) 100vw, 862px\" \/><\/a><figcaption>1. <a href=\"https:\/\/www.testcontainers.org\/\" target=\"_blank\" aria-label=\"Testcontainers w pracy testera (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Testcontainers w pracy testera<\/a><\/figcaption><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Docker dla testera manualnego<\/strong><\/h2>\n\n\n\n<p>Je\u015bli mia\u0142e\u015b do czynienia z bazami danych, to prawdopodobnie przypominasz sobie w\u0142asny wysi\u0142ek w\u0142o\u017cony w przygotowanie \u201eod zera\u201d \u015brodowisko pracy z DB. Nawet je\u015bli to tylko lokalne \u015brodowisko, musisz mie\u0107 drivera i klienta DB oraz w\u0142a\u015bciwie wszystko skonfigurowa\u0107, tworz\u0105c poprawny <em>connection string<\/em>. Szczeg\u00f3lnie wa\u017cne jest to w przypadku baz danych Oracle.<\/p>\n\n\n\n<p>Znacznie sprawniej mo\u017cna to zrobi\u0107 przy u\u017cyciu Dockera. <a href=\"https:\/\/hub.docker.com\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Centralne repozytorium obraz\u00f3w Dockera<\/a> zawiera ci\u0105gle rozwijan\u0105 baz\u0119 obraz\u00f3w, a w\u015br\u00f3d nich obrazy bazy danych Oracle. Producent prezentuje swoje oficjalne obrazy, ale te\u017c inni developerzy udost\u0119pniaj\u0105 w\u0142asne wersje. S\u0142owem \u2013 <strong>mamy wszystkie wersje niemal dowolnego narz\u0119dzia w jednym miejscu<\/strong>.<\/p>\n\n\n\n<p>Wybieram wersj\u0119 Oracle np. 9, reprezentowan\u0105 dockerowym tagiem i mog\u0119 zastosowa\u0107 komend\u0119 \u015bci\u0105gaj\u0105c\u0105 obraz na dysk lokalny:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/1-3.png\"><img decoding=\"async\" width=\"497\" height=\"207\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/1-3.png\" alt=\"Komenda \u015bci\u0105gaj\u0105ca obraz\" class=\"wp-image-20202\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/1-3.png 497w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/1-3-300x125.png 300w\" sizes=\"(max-width: 497px) 100vw, 497px\" \/><\/a><figcaption>Ryc. 2 Komenda \u015bci\u0105gaj\u0105ca obraz<\/figcaption><\/figure><\/div>\n\n\n\n<p>Korzy\u015b\u0107 jest znaczna, bo to, co pojawi si\u0119 na naszym dysku w postaci obrazu [<em>image<\/em>] dockerowego, zawiera ju\u017c wszystko, czego potrzebujemy, aby korzysta\u0107 z bazy danych. Kolejna komenda [<em>docker run<\/em>], kt\u00f3ra uruchomi kontener, sprawi, \u017ce baza danych b\u0119dzie ju\u017c dost\u0119pna do pracy.&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/2-3.png\"><img decoding=\"async\" width=\"1003\" height=\"218\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/2-3.png\" alt=\"Komenda uruchamiaj\u0105ca kontener\" class=\"wp-image-20204\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/2-3.png 1003w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/2-3-300x65.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/2-3-768x167.png 768w\" sizes=\"(max-width: 1003px) 100vw, 1003px\" \/><\/a><figcaption>Ryc. 3 <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/hub.docker.com\/r\/gvenzl\/oracle-xe\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Komenda uruchamiaj\u0105ca kontener<\/a><\/figcaption><\/figure><\/div>\n\n\n\n<p>W opisie danego obrazu autor najcz\u0119\u015bciej podaje gotowe komendy w r\u00f3\u017cnych wersjach. Wystarczy tylko uruchomi\u0107 je lokalnie w konsoli Windows.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Zalety i wady rozwi\u0105zania<\/strong><\/h3>\n\n\n\n<p>Nie twierdz\u0119, \u017ce obs\u0142uga Dockera jest trywialna, ale stawiam tez\u0119, \u017ce <strong>jak kto\u015b raz spr\u00f3buje, to ju\u017c nie wr\u00f3ci<\/strong> do wymagaj\u0105cych instalacji i \u017cmudnej konfiguracji wersji desktopowych.<\/p>\n\n\n\n<p>Istnieje te\u017c znaczna oszcz\u0119dno\u015b\u0107 przestrzeni dyskowej. Instalacja natywnego narz\u0119dzia prawie zawsze zajmuje wielokrotnie wi\u0119cej miejsca na dysku ni\u017c obraz dockerowy.<\/p>\n\n\n\n<p><strong>Do minus\u00f3w i niedogodno\u015bci<\/strong> nale\u017cy jednokrotna instalacja samego Dockera. Jest to narz\u0119dzie konsolowe, ale w systemie Windows mo\u017cna korzysta\u0107 w Docker Desktop, kt\u00f3re umo\u017cliwia przejrzyste zarz\u0105dzanie obrazami i kontenerami Dockera.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/3-3.png\"><img decoding=\"async\" width=\"957\" height=\"476\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/3-3.png\" alt=\"Docker desktop\" class=\"wp-image-20206\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/3-3.png 957w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/3-3-300x149.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/3-3-768x382.png 768w\" sizes=\"(max-width: 957px) 100vw, 957px\" \/><\/a><figcaption>Ryc. 4 <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/www.docker.com\/products\/docker-desktop\/\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Docker desktop<\/a><\/figcaption><\/figure><\/div>\n\n\n\n<p>Trzeba te\u017c przyzna\u0107, \u017ce musimy opanowa\u0107 konsolowe komendy, kt\u00f3re pozwol\u0105 swobodnie porusza\u0107 si\u0119 w Dockerze. Jest to jednak g\u0142\u00f3wnie wysi\u0142ek czasowy, bo z pe\u0142nym przekonaniem twierdz\u0119, \u017ce <a href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/cli\/\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >darmowe \u017ar\u00f3d\u0142a wiedzy na ten temat<\/a> w sieci s\u0105 wystarczaj\u0105ce i <strong>nie ma potrzeby inwestowa\u0107 w p\u0142atne szkolenia<\/strong> w przypadku testera manualnego.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Docker dla testera automatyzuj\u0105cego i programisty<\/strong><\/h2>\n\n\n\n<p>Programi\u015bci ju\u017c docenili Dockera i czerpi\u0105 z niego pe\u0142nymi gar\u015bciami. Kontener z dowoln\u0105 aplikacj\u0105 uruchamiamy b\u0142yskawicznie (kwestia milisekund), korzystamy z niej i zamykamy r\u00f3wnie szybko. Kiedy zapis stanu aplikacji nie ma dla nas znaczenia, to sprawa jest zupe\u0142nie bezproblemowa. Nie ma r\u00f3wnie\u017c k\u0142opotu, gdy potrzebna jest komunikacja mi\u0119dzy dyskiem lokalnym a kontenerem np. poprzez wys\u0142anie pliku do kontenera lub odebranie pliku z kontenera na dysk.<\/p>\n\n\n\n<p>Mo\u017cliwe jest tak\u017ce uruchomienie wielu kontener\u00f3w jednocze\u015bnie i po\u0142\u0105czenie ich w jedn\u0105 sie\u0107 \u2013 <em>Network.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Testcontainers<\/strong><\/h2>\n\n\n\n<p>Pojawi\u0142o si\u0119 jednak wyzwanie dla testowania aplikacji, kt\u00f3re korzystaj\u0105 z kontener\u00f3w, zw\u0142aszcza tych opartych na architekturze mikroserwisowej. Testy integracyjne to faza, podczas kt\u00f3rej chcemy zbada\u0107 wsp\u00f3lne dzia\u0142anie r\u00f3\u017cnych komponent\u00f3w systemu np. API Restowe i baza danych czy te\u017c mikroserwis produkuj\u0105cy wiadomo\u015bci na Kafk\u0119 i konsumuj\u0105cy te wiadomo\u015bci wraz z brokerem kafkowym.<\/p>\n\n\n\n<p>Przygotowuj\u0105c automatyczne testy integracyjne dla takiej architektury, <strong>niezb\u0119dne jest wykorzystanie kontenera bazy danych<\/strong> np. PostgreSQL czy kolejki np. Kafka. Kontener ten musi by\u0107 uruchamiany w kodzie.<\/p>\n\n\n\n<p>Tutaj w\u0142a\u015bnie na scen\u0119 wchodzi Testcontainers. Jest to<strong> biblioteka, kt\u00f3ra dostarcza API do obs\u0142ugi kontener\u00f3w dockerowych w kodzie<\/strong>. Narz\u0119dzie obs\u0142uguje nast\u0119puj\u0105ce j\u0119zyki programowania:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Java,<\/li><li>Go,<\/li><li>.NET,<\/li><li>Python,<\/li><li>Node.js,<\/li><li>Rust.<\/li><\/ul>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4a.png\"><img decoding=\"async\" width=\"873\" height=\"198\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4a.png\" alt=\"J\u0119zyki programowania obs\u0142ugiwane przez Testcontainers\" class=\"wp-image-20209\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4a.png 873w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4a-300x68.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4a-768x174.png 768w\" sizes=\"(max-width: 873px) 100vw, 873px\" \/><\/a><figcaption>Ryc. 5 <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/www.testcontainers.org\/\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >J\u0119zyki programowania obs\u0142ugiwane przez Testcontainers<\/a><\/figcaption><\/figure><\/div>\n\n\n\n<p>Biblioteka dostarcza automatyczne zarz\u0105dzanie uruchamianiem i zamykanie kontener\u00f3w zaraz po zako\u0144czonym te\u015bcie. To niezwykle \u201eestetyczne\u201d podej\u015bcie. \u015arodowisko testowanej aplikacji jest budowane stosunkowo szybko (ok. 2-3 dodatkowe sekundy) automatycznie zaraz przed testem. Nast\u0119pnie wykonywana jest logika naszych scenariuszy i zwracany rezultat, a na koniec kontener znika bezpowrotnie.<\/p>\n\n\n\n<p>Ta \u201eefemeryczno\u015b\u0107\u201d dla testera automatyzuj\u0105cego jest niezwykle atrakcyjna. Testcontainers dostarcza mechanizm przygotowania \u201ew locie\u201d danych testowych. Znika r\u00f3wnie\u017c potrzeba czyszczenia stanu po te\u015bcie w trosce o to, by artefakty i pozostawione dane nie wp\u0142ywa\u0142y przypadkiem na nast\u0119pne egzekucje tego samego testu. Tutaj ka\u017cde uruchomienie testu posiada w\u0142asn\u0105, \u201e\u015bwie\u017c\u0105\u201d instancj\u0119 komponentu, kt\u00f3ra ginie po te\u015bcie wraz z ca\u0142\u0105 swoj\u0105 zawarto\u015bci\u0105.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Testcontainers\/ryuk<\/strong><\/h3>\n\n\n\n<p>Obiekt, kt\u00f3ry zarz\u0105dza zamykaniem wybranych kontener\u00f3w to \u201e<strong>testcontainers\/ryuk<\/strong>\u201d. Uruchamia si\u0119 zawsze, mimo i\u017c wprost nie deklarujemy tego w kodzie. To <strong>kontener uprzywilejowany<\/strong>, kt\u00f3ry odpowiada za prawid\u0142owe i automatyczne zamykanie kontener\u00f3w <em>[automatic cleanup].<\/em><\/p>\n\n\n\n<p>Mechanizm ten ma du\u017ce znaczenie zw\u0142aszcza, kiedy ko\u0144czymy egzekucj\u0119 test\u00f3w w niestandardowy spos\u00f3b np.:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>pojawia si\u0119 exception w trakcie wykonania scenariusza,<\/li><li>nie uda\u0142o si\u0119 uruchomi\u0107 pe\u0142nego \u015brodowiska do test\u00f3w, bo zabrak\u0142o jednego kontenera,<\/li><li>w czasie debugowania zamykamy test z poziomu IDE.<\/li><\/ul>\n\n\n\n<p>Wszystkie te i podobne sytuacje powoduj\u0105 wymuszenie zamkni\u0119cia uruchomionych kontener\u00f3w, aby nie pozostawi\u0107 w pami\u0119ci \u201emartwych kontener\u00f3w\u201d. <em>&nbsp;<\/em>Dokumentacja biblioteki pozwala wprawdzie na w\u0142asn\u0105 konfiguracj\u0119 <strong>ryuk,<\/strong> a nawet wy\u0142\u0105czenie go, ale jest to niewskazane.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Testcontainers dla j\u0119zyka JAVA \u2013 demo<\/strong><\/h2>\n\n\n\n<p>Przedstawi\u0119 praktyczny przyk\u0142ad u\u017cycia Testcontainers w kodzie Java. Chc\u0119 wykorzysta\u0107 baz\u0119 danych PostgreSQL jako komponent podczas testowania kontrolera [<em>Controller<\/em>] w prostym microservice. Wybra\u0142em framework Micronaut, ale r\u00f3wnie dobrze zadzia\u0142a to z tradycyjnym Spring Boot.<\/p>\n\n\n\n<p>Serwis to klasyczny CRUD \u2013 pozwala na odczyt, zapis, edycj\u0119 i usuwanie encji Actor do bazy danych. Posiada podzia\u0142 na warstwy:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Controller,<\/li><li>Service,<\/li><li>Repository.<\/li><\/ul>\n\n\n\n<p>W Micronaucie taka klasa modelowa b\u0119dzie wygl\u0105da\u0142a nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage com.example;\n\nimport io.micronaut.core.annotation.Nullable;\nimport io.micronaut.data.annotation.GeneratedValue;\nimport io.micronaut.data.annotation.MappedEntity;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport javax.persistence.Entity;\nimport javax.persistence.GenerationType;\nimport javax.persistence.Id;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\n\n@Schema(description=&quot;Actor business model&quot;)\n@Entity\npublic class Actor\n {\n     @GeneratedValue @Id\n     @Nullable\n     private Long id;\n     @NotBlank @Size(max = 20)\n     private String firstName;\n     @NotBlank @Size(max = 20)\n     private String lastName;\n     @NotBlank\n     private Long rating;\n\n\/\/getters and setters\n}\n&#x5B;src\/main\/java\/model\/Actor] \n<\/pre><\/div>\n\n\n<p>Zdefiniowa\u0142em DataSource w pliku konfiguracyjnym <strong>application.yml<\/strong> dla Postgresa, kt\u00f3ry wygl\u0105da nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\ndatasources:\n  default:\n    url: jdbc:postgresql:\/\/localhost:5432\/actor\n    driverClassName: org.postgresql.Driver\n    username: postgres\n    password: postgres\n    schema-generate: NONE\n    dialect: POSTGRES\n    schema: public\n<\/pre><\/div>\n\n\n<p>Kiedy baza danych z odpowiednim schematem jest uruchomiona, wszystko dzia\u0142a bez zarzutu. Mog\u0119 wykona\u0107 moje testy API napisane np. w RestAssured. Je\u015bli jednak wy\u0142\u0105cz\u0119 baz\u0119 danych, \u017caden test nie da wiarygodnych rezultat\u00f3w. Testcontainer pozwoli mi na \u201ew\u0142\u0105czenie\u201d brakuj\u0105cego komponentu tylko na czas wykonania moich test\u00f3w.<\/p>\n\n\n\n<p>Pierwszym krokiem jest pobranie zale\u017cno\u015bci do projektu:<\/p>\n\n\n\n<p><strong>Maven<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n&lt;dependency&gt;\n    &lt;groupId&gt;org.junit.jupiter&lt;\/groupId&gt;\n    &lt;artifactId&gt;junit-jupiter&lt;\/artifactId&gt;\n    &lt;version&gt;5.8.1&lt;\/version&gt;\n    &lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n    &lt;groupId&gt;org.testcontainers&lt;\/groupId&gt;\n    &lt;artifactId&gt;testcontainers&lt;\/artifactId&gt;\n    &lt;version&gt;1.17.6&lt;\/version&gt;\n    &lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n    &lt;groupId&gt;org.testcontainers&lt;\/groupId&gt;\n    &lt;artifactId&gt;junit-jupiter&lt;\/artifactId&gt;\n    &lt;version&gt;1.17.6&lt;\/version&gt;\n    &lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n<\/pre><\/div>\n\n\n<p><strong>Gradle<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\ntestImplementation &quot;org.junit.jupiter:junit-jupiter:5.8.1&quot;\ntestImplementation &quot;org.testcontainers:testcontainers:1.17.6&quot;\ntestImplementation &quot;org.testcontainers:junit-jupiter:1.17.6&quot;\n<\/pre><\/div>\n\n\n<p>Nast\u0119pnie definiuj\u0119 DataSource na poziomie test\u00f3w. W Micronaut odpowiedzialny jest za to plik konfiguracyjny <strong>application-test.yml:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\ndatasources:\ndefault:\nurl: jdbc:tc:postgresql:latest:\/\/\/postgres?TC_INITSCRIPT=file:src\/test\/resources\/init-actor-testdata.sql?TC_DAEMON=true\n    \tdriverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver\n    \tminimum-idle: 5\n<\/pre><\/div>\n\n\n<p>Zwr\u00f3\u0107cie uwag\u0119, \u017ce parametr <em>url<\/em> jest oznaczony przez <strong>tc<\/strong> \u2013 [jdbc:tc] \u2013 co wskazuje, \u017ce jego obs\u0142ug\u0105 zajmie si\u0119 Testcontainers. Dodatkowo, <em>driverClassName <\/em>r\u00f3wnie\u017c zawiera wskazanie na pakiet org.testcontainers.<\/p>\n\n\n\n<p>Teraz ju\u017c mo\u017cemy w kodzie klasy testowej zaznaczy\u0107, \u017ce b\u0119dziemy wykorzystywali kontenery Dockera poprzez adnotacj\u0119 <strong>@Testcontainers<\/strong> przed klas\u0105.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Testcontainers\n@MicronautTest(environments = &quot;test&quot;)\n@Slf4j\npublic class JdbcTemplateActorTest {\n}\n&#x5B;src\/test\/java\/ JdbcTemplateActorTest]\n<\/pre><\/div>\n\n\n<p>oraz adnotacj\u0105 <strong>@Container<\/strong> przed polem, kt\u00f3ry definiuje typ I wersj\u0119 kontenera:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Testcontainers\n@MicronautTest(environments = &quot;test&quot;)\n@Slf4j\npublic class JdbcTemplateActorTest {\n\n    @Container\n    private static final PostgreSQLContainer&lt;?&gt; postgres = PostgresContainer.getContainerPostgres();\n\n&#x5B;src\/test\/java\/ JdbcTemplateActorTest]\n<\/pre><\/div>\n\n\n<p>Pozosta\u0142e adnotacje to:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>@MicronautTest(environments = &#8222;test&#8221;) \u2013 micronautowe oznaczenie, \u017ce b\u0119dziemy korzysali z testowego kontekstu Micronauta.<\/li><li>@Slf4j \u2013 adnotacja Lombok uruchamiaj\u0105ca logger w klasie.<\/li><\/ul>\n\n\n\n<p>Klasa <em>PostgreSQLContainer <\/em>zapewnia nam prawid\u0142ow\u0105 obs\u0142ug\u0119 tego szczeg\u00f3\u0142owego kontenera PostgreSQL. Istnieje ponadto generyczna klasa <em>GenericContainer<\/em>, kt\u00f3r\u0105 zawsze mo\u017cemy wykorzysta\u0107 do przechowania obiektu dowolnego kontenera:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Container\n    private static final GenericContainer&lt;?&gt; postgres = PostgresContainer.getContainerPostgres();\n\n<\/pre><\/div>\n\n\n<p>Biblioteka Testcontainers wymaga, aby\u015bmy dodali absolutnie minimaln\u0105 i niezb\u0119dn\u0105 konfiguracj\u0119 naszego kontenera. Musimy przecie\u017c wybra\u0107, jaki image postgres potrzebujemy, zdefiniowa\u0107 nazw\u0119 schematu, username i password.<\/p>\n\n\n\n<p>Osobi\u015bcie preferuj\u0119 umieszczenie takiej konfiguracji nie w klasie testowej, ale w osobnej klasie odpowiedzialnej za kontenery postgresa. Ma to sens, poniewa\u017c postgres to nie jedyny komponent, jakiego potencjalnie mo\u017cemy potrzebowa\u0107. Gdy pojawi si\u0119 Kafka, Redis czy MongoDB, to <strong>bezpiecznie odseparujemy wszystkie te konfiguracje.<\/strong><\/p>\n\n\n\n<p>Kod takiej klasy mo\u017ce wygl\u0105da\u0107 nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\npackage com.example.containers;\n\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class PostgresContainer extends PostgreSQLContainer&amp;lt;PostgresContainer&gt; {\n    private static final String MYPOSTGRESIMAGE = &quot;postgres:latest&quot; ;\n    private static final String MYTESTDATABASENAME = &quot;actor&quot;;\n    private static final String USERNAME = &quot;postgres&quot;;\n    private static final String PASSWD = &quot;postgres&quot;;\n    private static final Integer DB_PORT = 5432;\n\n    private PostgresContainer() {\n        super(DockerImageName.parse(MYPOSTGRESIMAGE));\n    }\n\n    public static PostgreSQLContainer&amp;lt;?&gt; getContainerPostgres() {\n        return new PostgreSQLContainer&amp;lt;&gt;(DockerImageName.parse(MYPOSTGRESIMAGE))\n                .withDatabaseName(MYTESTDATABASENAME)\n                .withExposedPorts(DB_PORT)\n                .withUsername(USERNAME)\n                .withPassword(PASSWD);\n    }\n}\n&#x5B;src\/test\/containers\/PostgresContainer]\n<\/pre><\/div>\n\n\n<p>Najistotniejsza jest tu statyczna metoda \u2013 <em>getContainerPostgres<\/em>(). To tutaj za pomoc\u0105 wielu metod rozpoczynaj\u0105cych si\u0119 od \u201ewith\u201d ustawiamy stan naszego kontenera oraz sposoby \u0142\u0105czenia si\u0119 z nim.<\/p>\n\n\n\n<p>Tryb \u0142a\u0144cuchowego wywo\u0142ania tych metod dodaje ogromnej wygody:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"906\" height=\"492\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4-2.png\" alt=\"tryb \u0142a\u0144cuchowy wywo\u0142ania metody\" class=\"wp-image-20212\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4-2.png 906w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4-2-300x163.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/4-2-768x417.png 768w\" sizes=\"(max-width: 906px) 100vw, 906px\" \/><\/figure>\n\n\n\n<p>Teraz pozostaje ju\u017c tylko przekazanie do testowego DataSource szczeg\u00f3\u0142\u00f3w po\u0142\u0105czenia z kontenerow\u0105 baz\u0105 danych. Wraz z micronautow\u0105 adnotacj\u0105 @MockBean jest to bardzo proste i mo\u017ce wygl\u0105da\u0107 nast\u0119puj\u0105co w klasie testowej:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Testcontainers\n@MicronautTest(environments = &quot;test&quot;)\n@Slf4j\npublic class JdbcTemplateActorTest {\n\n    @Container\n    private static final PostgreSQLContainer&lt;?&gt; postgres = PostgresContainer.getContainerPostgres();\n\n    private DataSource postgresDataSource;\n\n    private JdbcTemplate jdbcTemplate;\n\n    @Inject\n    public JdbcTemplateExampleTest(DataSource postgresDataSource) {\n        this.postgresDataSource = postgresDataSource;\n    }\n\n    @MockBean(DBConnector.class)\n    DBConnector postgresConnection() {\n        PostgresTestContainer dbConnection = new PostgresTestContainer();\n        dbConnection.setUrl(postgres.getJdbcUrl());\n        dbConnection.setUsername(postgres.getUsername());\n        dbConnection.setPasswd(postgres.getPassword());\n        return dbConnection;\n    }\n&#x5B;src\/test\/java\/ JdbcTemplateActorTest]\n<\/pre><\/div>\n\n\n<p>Widoczny w klasie bean JdbcTemplate pos\u0142u\u017cy\u0142 mi tylko jako connector do bazy danych. To dzi\u0119ki niemu b\u0119dziemy wiedzieli, \u017ce baza danych rzeczywi\u015bcie pojawi si\u0119, kiedy jej potrzebujemy, bo b\u0119dziemy mogli wykona\u0107 nasze zapytania i odebra\u0107 rezultaty.<\/p>\n\n\n\n<p>Kiedy wszystko jest przygotowane mog\u0119 zaimplementowa\u0107 dwa proste testy:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Test 1 \u2013 shouldGetAllActorsFromDBBasedOnTestContainers() \u2013 pobranie wszystkich aktor\u00f3w z bazy \u2013 \u201eSELECT * FROM actor\u201d;<\/li><li>Test 2 \u2013 shouldGetSingleActorFromDBBasedOnTestContainers() \u2013 pobranie jednego aktora z bazy \u2013 \u201eSELECT firstname FROM actor WHERE id=1\u201d;<\/li><\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Test\n    void shouldGetAllActorsFromDBBasedOnTestContainers() {\n        jdbcTemplate = new JdbcTemplate(postgresDataSource);\n        await().atMost(10, TimeUnit.SECONDS)\n                .until(this::isRecordLoaded);\n\n        var dbResultsSize = this.getLoadedRecords().size();\n        var dbResults = this.getLoadedRecords();\n\n        assertThat(dbResultsSize).isEqualTo(3);\n        assertThat(dbResults.get(0).get(&quot;firstname&quot;)).isEqualTo(&quot;Brad&quot;);\n        assertThat(dbResults.get(1).get(&quot;firstname&quot;)).isEqualTo(&quot;Angelina&quot;);\n        assertThat(dbResults.get(2).get(&quot;firstname&quot;)).isEqualTo(&quot;Salma&quot;);\n    }\n\n    @Test\n    void shouldGetSingleActorFromDBBasedOnTestContainers() {\n        jdbcTemplate = new JdbcTemplate(postgresDataSource);\n        await().atMost(10, TimeUnit.SECONDS)\n                .until(this::isRecordLoaded);\n\n        var dbResultsSize = this.getLoadedRecords().size();\n        var dbResults = this.getSingleRecord();\n\n        assertThat(dbResultsSize).isEqualTo(3);\n        assertThat(dbResults).isEqualTo(&quot;Brad&quot;);\n    }\n\n    private boolean isRecordLoaded() {\n        return jdbcTemplate.queryForList(&quot;Select * from actor&quot;).size() &gt; 1;\n    }\n\n    private List&lt;Map&lt;String, Object&gt;&gt; getLoadedRecords() {\n        return jdbcTemplate.queryForList(&quot;Select * from actor&quot;);\n    }\n\n    private String getSingleRecord() {\n        return jdbcTemplate.queryForObject(&quot;Select firstname from actor where id=1&quot;, String.class);\n    }\n\n&#x5B;src\/test\/java\/ JdbcTemplateActorTest]\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Wyniki<\/strong><\/h3>\n\n\n\n<p>Oto rezultaty klasy testowej uruchomionej lokalnie w IDE. Zwr\u00f3\u0107 uwag\u0119, jak logi w konsoli wyra\u017anie wskazuj\u0105, \u017ce:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>uruchomi\u0142 si\u0119 Docker,<\/li><li>uruchomi\u0142 si\u0119 uprzywilejowany kontener Testcontainers\/ryuk,<\/li><li>uruchomi\u0142 si\u0119 kontener PostgreSQL<\/li><\/ul>\n\n\n\n<p>I co najwa\u017cniejsze \u2013 czas wykonania obu test\u00f3w to <strong>zaledwie 0,5 sek!<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"383\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2-1024x383.png\" alt=\"Rezultaty klasy testowej uruchomionej w lokalnej IDE\" class=\"wp-image-20214\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2-1024x383.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2-300x112.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2-768x287.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2-1536x574.png 1536w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/5-2.png 1847w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>Ryc. 6 Rezultaty klasy testowej uruchomionej w lokalnej IDE<\/figcaption><\/figure>\n\n\n\n<p>Dodatkowo, na potwierdzenie dodaj\u0119 jeszcze widok z Docker Desktop w czasie wykonania test\u00f3w. Wida\u0107 wyra\u017anie, \u017ce potrzebne kontenery s\u0105 w statusie running:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2.png\"><img decoding=\"async\" width=\"1024\" height=\"268\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2-1024x268.png\" alt=\"Widok z Docker Desktop w czasie wykonania test\u00f3w\" class=\"wp-image-20216\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2-1024x268.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2-300x78.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2-768x201.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/6-2.png 1244w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Ryc. 7 Widok z Docker Desktop w czasie wykonania test\u00f3w<\/figcaption><\/figure><\/div>\n\n\n\n<p>Mo\u017cna zauwa\u017cy\u0107, jak Testcontainer pozwoli\u0142 mi minimalnym wysi\u0142kiem implementowa\u0107 dowolne testy integracyjne. Niezb\u0119dne komponenty \u201ewstaj\u0105\u201d w postaci kontener\u00f3w dockerowych, a po wykonanym te\u015bcie komponenty te s\u0105 usuwane.<\/p>\n\n\n\n<p>Jest jeszcze jedno zagadnienie, kt\u00f3re uznaj\u0119 jako niezwykle przydatne w Testcontainers. S\u0105 to <strong>opcje (flagi), kt\u00f3re definiuj\u0119 w pliku konfiguracyjnym<\/strong> <em>application-test.yml:<\/em><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>TC_INITSCRIPT<\/li><li>TC_DAEMON<\/li><\/ul>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2.png\"><img decoding=\"async\" width=\"1024\" height=\"139\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2-1024x139.png\" alt=\"Flagi definiowane w pliku konfiguracyjnym application-test.yml \" class=\"wp-image-20218\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2-1024x139.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2-300x41.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2-768x104.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/7-2.png 1514w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Ryc. 8 Flagi definiowane w pliku konfiguracyjnym application-test.yml<\/figcaption><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>TC_INITSCRIPT<\/strong><\/h3>\n\n\n\n<p>Zrozumia\u0142e jest, \u017ce kiedy korzystamy z bazy danych, chcieliby\u015bmy, \u017ceby ju\u017c by\u0142 tam schemat, tabele i jakie\u015b dane testowe niezb\u0119dne do wykonania testu. Dzi\u0119ki opcji TC_INITSCRIPT, mo\u017cemy zdefiniowa\u0107 skrypt SQL, kt\u00f3ry wykona si\u0119 zaraz po uruchomieniu kontenera DB, ale przed wykonaniem pierwszej linijki kodu.<\/p>\n\n\n\n<p>W moim demo wykorzysta\u0142em nast\u0119puj\u0105cy skrypt, dzi\u0119ki czemu nie musia\u0142em w kodzie testu obs\u0142ugiwa\u0107 zawarto\u015bci mojej bazy PostgreSQL:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2.png\"><img decoding=\"async\" width=\"1024\" height=\"396\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2-1024x396.png\" alt=\"Skrypt src\/test\/resources\/init-actor-testdata.sql\" class=\"wp-image-20220\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2-1024x396.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2-300x116.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2-768x297.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/8-2.png 1516w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Ryc. 9 Skrypt src\/test\/resources\/init-actor-testdata.sql<\/figcaption><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>TC_DAEMON<\/strong><\/h3>\n\n\n\n<p>W domy\u015blnym ustawieniu kontener bazy danych jest zatrzymywany po zamkni\u0119ciu ostatniego po\u0142\u0105czenia. Bywaj\u0105 jednak sytuacje, w kt\u00f3rych b\u0119dziemy chcieli, aby kontener dzia\u0142a\u0142 do momentu jego wyra\u017anego zatrzymania lub wy\u0142\u0105czenia maszyny JVM. Aby to zrobi\u0107, dodaj parametr TC_DAEMON do adresu URL jak w grafice wy\u017cej.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Podsumowanie<\/strong><\/h2>\n\n\n\n<p>Testcontainers to biblioteka do obs\u0142ugi kontener\u00f3w dockerowych w kodzie. <strong>Doskonale sprawdza si\u0119<\/strong> podczas tworzenia efemerycznych komponent\u00f3w w zautomatyzowanych testach integracyjnych. Pozwala te\u017c na za\u0142adowanie skrypt\u00f3w SQL w taki spos\u00f3b, \u017ce kontenerowa baza danych jest ju\u017c wyposa\u017cona w schemat i dane niezb\u0119dne do test\u00f3w.<\/p>\n\n\n\n<p>Podczas pracy w Testcontainers zauwa\u017cy\u0142em <strong>dwie niedogodno\u015bci<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>trzeba mie\u0107 do\u015bwiadczenie w pracy z zewn\u0119trznymi bibliotekami Java. Samodzielna implementacja nie jest trywialna, jak nietrywialny jest sam Docker. Pr\u00f3g intelektualnego wej\u015bcia jest zatem zauwa\u017calny, ale widz\u0119 wyra\u017anie wysi\u0142ek tw\u00f3rc\u00f3w tej biblioteki, \u017ceby mo\u017cliwie upro\u015bci\u0107 developerom t\u0119 prac\u0119,<\/li><li>po ca\u0142odziennej pracy w Testcontainers i wielokrotnym uruchamianiu test\u00f3w integracyjnych zauwa\u017cam, \u017ce Docker, zw\u0142aszcza Docker Desktop w Windows OS, potrafi konsumowa\u0107 du\u017c\u0105 ilo\u015b\u0107 zasob\u00f3w komputera (RAM, procesor). Niejednokrotnie kontenery nie \u201ewstaj\u0105\u201d, a test ko\u0144czy si\u0119 b\u0142\u0119dem \u201eInitialization error\u201d. Mo\u017cemy wtedy skonfigurowa\u0107 w pliku <em>.wslconfig<\/em> zakres dedykowanych zasob\u00f3w dla Dockera. Kiedy b\u0142\u0105d inicjalizacji kontener\u00f3w si\u0119 powtarza, najskuteczniejszym sposobem jest zwyk\u0142y restart komputera.<\/li><\/ul>\n\n\n\n<p>***<br>Je\u017celi interesuje Ci\u0119 temat Dockera, zach\u0119camy do przeczytania serii artyku\u0142\u00f3w przygotowanych przez naszego eksperta:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/sii.pl\/blog\/docker-dla-programistow-co-to-jest\/?category=development-na-twardo&amp;tag=devops,docker,kontener\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\">Docker dla programist\u00f3w: co to jest?<\/a><\/li><li><a href=\"https:\/\/sii.pl\/blog\/docker-dla-programistow-instalacja-i-budowanie-pierwszego-srodowiska\/?category=development-na-twardo&amp;tag=devops,docker,postgresql\" target=\"_blank\" rel=\"noreferrer noopener\">Docker dla programist\u00f3w \u2013 budowa pierwszego \u015brodowiska. Cz\u0119\u015b\u0107 I<\/a><\/li><li><a href=\"https:\/\/sii.pl\/blog\/docker-dla-programistow-budowa-srodowiska-rozwojowego-cz-2\/?category=development-na-twardo&amp;tag=camunda,devops,docker\" target=\"_blank\" rel=\"noreferrer noopener\">Docker dla programist\u00f3w \u2013 budowa \u015brodowiska rozwojowego. Cz\u0119\u015b\u0107 II<\/a><\/li><li><a href=\"https:\/\/sii.pl\/blog\/docker-dla-programistow-dystrybucja-aplikacji-cz-3\/?category=development-na-twardo&amp;tag=devops,docker,docker-maven-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">Docker dla programist\u00f3w \u2013 dystrybucja aplikacji&nbsp;<\/a><\/li><\/ul>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;20201&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;10&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 10)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Testcontainers \u2013 Docker w pracy testera&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 10)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Spo\u015br\u00f3d wielu narz\u0119dzi, kt\u00f3re s\u0105 ju\u017c dost\u0119pne i tych, ci\u0105gle pojawiaj\u0105cych si\u0119 w dynamicznym \u015bwiecie IT, Testcontainers zwr\u00f3ci\u0142o moj\u0105 szczeg\u00f3ln\u0105 &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/testcontainers-docker-w-pracy-testera\/\">Continued<\/a><\/p>\n","protected":false},"author":485,"featured_media":20227,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":9,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1317],"tags":[1546,153,770,291],"class_list":["post-20201","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-testowanie","tag-przeglad-narzedzi","tag-docker","tag-programowanie","tag-tester"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/03\/TestContainers-docker-w-pracy-testera-.jpg","category_names":["Testowanie"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/20201"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/485"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=20201"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/20201\/revisions"}],"predecessor-version":[{"id":20226,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/20201\/revisions\/20226"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/20227"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=20201"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=20201"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=20201"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}