برنامه نویسی

برابری توسعه دهنده/تولیدکننده: کانتینرهای تست بوت بهار

مقدمه

هدف برابری توسعه دهنده/تولید کاهش شکاف بین محیط های توسعه و تولید است. این مقاله شکاف ابزارها را، به ویژه در تست یکپارچه سازی با کانتینرهای تست اسپرینگ، به عنوان راهی برای مشابه ساختن توسعه و تولید تا حد امکان مورد هدف قرار می دهد.
هنگام انجام تست های یکپارچه سازی شامل پایگاه های داده، باید تمام عملیات CRUD را به دقت مدیریت کنیم. این در یک محیط پایگاه داده متمرکز بسیار مهم است، جایی که یک تست، مانند TestDeleteUserByID_ShouldReturnOk()، ممکن است «به طور تصادفی» تصمیم بگیرد حساب وفادارترین مشتری ما را که از سال 2015 با ما بوده است پاک کند 🤦‍♂️
برای کاهش چنین خطراتی، می‌توانیم راه‌حل‌هایی مانند تراکنش‌های پایگاه داده را برای جداسازی داده‌های آزمایشی در نظر بگیریم. به عنوان مثال، یک آزمایش می تواند یک تراکنش را برای اصلاح داده ها شروع کند و سپس در پایان به عقب برگردد و در نتیجه پایگاه داده را در حالت اولیه خود باقی بگذارد.
با این حال، این یک مسئله مهم را ایجاد می کند: چه چیزی تست را آزمایش می کند؟

توضیحات تصویر

اگر انزوا با شکست مواجه شود و کد تغییراتی را اجرا کند که به نوعی بازگردانده نمی شوند و منجر به نشت داده ها به محیط تولید می شود؟ آسیب احتمالی در چنین سناریوهایی قابل توجه است.

از طرف دیگر، آزمایش مستقل با یک پایگاه داده در حافظه مانند H2DB نیز چالش هایی را ایجاد می کند. حتی اگر راه‌اندازی آن آسان باشد، H2DB با RDBMS متفاوت است، بنابراین احتمال زیادی وجود دارد که آزمایش‌ها بین محیط‌های توسعه و تولید نتایج متفاوتی داشته باشند، بنابراین ما نمی‌توانیم به این نتایج اعتماد کنیم.

https://stackoverflow.com/questions/62778900/syntax-error-h2-database-in-postgresql-compatibility

راه حل کمتر مشکل بعدی، شبیه سازی پایگاه داده، ارائه یک رویکرد کم خطرتر با محیطی شبیه به تولید است. با این حال، این روش با محدودیت هایی همراه است. با توجه به اینکه ORM ها ایجاد و راه اندازی طرح پایگاه داده تولید را خودکار می کنند، باید به این فکر کنیم که چگونه پایگاه داده توسعه کلون شده را هماهنگ نگه داریم.


هر چیزی را که می توانید کانتینری کنید آزمایش کنید: پایگاه داده، کارگزار پیام و موارد دیگر

Testcontainers یک کتابخانه جاوا است که از تست‌های JUnit پشتیبانی می‌کند، نمونه‌های سبک و دور ریختنی از پایگاه‌های داده رایج، مرورگرهای وب سلنیوم یا هر چیز دیگری که می‌تواند در ظرف Docker اجرا شود را ارائه می‌کند.

در ابتدا برای جاوا توسعه داده شد، اما از آن زمان برای پشتیبانی از زبان های دیگر مانند Go، Rust و .NET گسترش یافته است.

ایده اصلی Testcontainers ارائه یک زیرساخت بر اساس تقاضا، قابل اجرا از IDE است، که در آن تست‌ها را می‌توان بدون نیاز به تمسخر یا استفاده از سرویس‌های درون حافظه و با پاک‌سازی خودکار انجام داد.

ما می توانیم در سه مرحله به این امر دست یابیم:

  • خدمات مورد نیاز را راه اندازی کنید و زیرساخت را با راه اندازی کانتینرهای Docker آماده کنید و برنامه خود را برای استفاده از این تنظیمات به عنوان زیرساخت آزمایشی پیکربندی کنید.
  • آزمایشات خود را بر روی زیرساخت docker شده اجرا کنید.
  • پس از پایان آزمایشات، زیرساخت docker شده را به طور خودکار تمیز کنید

مستندات کتابخانه Testcontainers


اجرای تست کانتینرهای بوت فنری

در ApplicationIntegrationTests که کلاس پایه برای تست یکپارچه سازی است، یک PostgreSQLContainer ثابت تعریف می کنیم. این کانتینر در تمام نمونه های آزمایشی مشتق شده از این کلاس استفاده می شود.

حاشیه‌نویسی @Testcontainers امکان کشف تمام فیلدهای حاشیه‌نویسی شده با @Container، مدیریت روش‌های چرخه حیات کانتینر و راه‌اندازی کانتینرها را می‌دهد.

  • کانتینرهای اعلام شده به عنوان میدان های ثابت بین روش های آزمایش به اشتراک گذاشته می شوند. آنها فقط یک بار قبل از اجرای هر روش تست شروع می شوند و پس از اجرای آخرین روش تست متوقف می شوند.
  • کانتینرهای اعلام شده به عنوان فیلدهای نمونه برای هر روش آزمایشی شروع و متوقف می شوند.

حاشیه نویسی @DynamicPropertySource به ما اجازه می دهد تا خصوصیات را به صورت پویا به محیط آزمایش خود تزریق کنیم.

@Testcontainers
@ActiveProfiles("test")
public abstract class ApplicationIntegrationTests {
    @Container
    protected static PostgreSQLContainer> postgres=new PostgreSQLContainer<>("postgres:17.2-alpine")
            .withDatabaseName("testcontainersproject")
            .withUsername("root")
            .withPassword("root");

    @DynamicPropertySource
    static void initialize(DynamicPropertyRegistry registry)
    {
        registry.add("spring.datasource.url",postgres::getJdbcUrl);
        registry.add("spring.datasource.username",postgres::getUsername);
        registry.add("spring.datasource.password",postgres::getPassword);
    }


}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

از طرف دیگر، می‌توانیم استفاده از @Testcontainers و @Container را نادیده بگیریم و در عوض، چرخه حیات کانتینر را مستقیماً با استفاده از @BeforeAll و @AfterAll مدیریت کنیم. این رویکرد اجازه می دهد تا کنترل بیشتری روی زمان و چگونگی راه اندازی و توقف کانتینرها انجام شود

@BeforeAll
public static void runContainer(){
        postgres.start();
}
@AfterAll
static void stopContainers() {
    postgres.stop();
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در روش @AfterAll callback، ما به صراحت ظرف Postgres را متوقف می کنیم. با این حال، حتی اگر صراحتاً کانتینر را متوقف نکنیم، Testcontainers به ​​طور خودکار ظرف‌ها را در پایان اجرای آزمایشی تمیز و خاموش می‌کند.

اکنون می توانیم تست های یکپارچه سازی را با گسترش ApplicationIntegrationTests به صورت زیر ایجاد کنیم.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CategoryControllerTest extends ApplicationIntegrationTests {
    private static final String CATEGORY_ENDPOINT="/categories";
   @Autowired
    private MockMvc mockMvc;
    @Autowired
    private CategoryRepository categoryRepository;

    @Test
    void TestGetAllCategories_ShouldReturnOk() throws Exception {

        List<Category> categories = List.of(
                new Category("Electronics", "All kinds of electronic gadgets from smartphones to laptops"),
                new Category("Books", "A wide range of books from novels to educational textbooks")
        );
        categoryRepository.saveAll(categories);
        MvcResult mvcResult=mockMvc.perform(
                get(CATEGORY_ENDPOINT).
                        contentType(MediaType.APPLICATION_JSON)
        )
                .andExpect(status().isOk())
                .andReturn();
        var response=mvcResult.getResponse().getContentAsString();
        assertNotNull(response);
        assertFalse(response.isEmpty());
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا