<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های fdbvcf</title>
        <link>https://virgool.io/feed/@beanvortex</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-04-14 19:56:49</pubDate>
        <image>
            <url>https://static.virgool.io/images/default-avatar.jpg</url>
            <title>fdbvcf</title>
            <link>https://virgool.io/@beanvortex</link>
        </image>

                    <item>
                <title>بررسی عمکرد spring webflux و spring mvc</title>
                <link>https://virgool.io/Rocket/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%DA%A9%D8%B1%D8%AF-spring-webflux-%D9%88-spring-mvc-rjekpxko6yut</link>
                <description>بنام خداتو این پست در مورد این بحث میکنیم که spring webflux که بصورت reactive کار میکنه، چقدر با spring mvc تفاوت های عملکردی و عملیاتی داره.قبل از ادامه مطلب این نکات رو در نظر بگیرید:۱. پردازش ها به ۳ صورت در زبان های امروزی اجرا میشن:الف) synchronous blocking : به این صورت که یک تردی داریم که میتونه برنامه رو بلاک یا فریز کنه . spring mvc در این دسته قرار دارهب) asynchronous blocking : به این صورت که چندین ترد داریم ولی باز این ترد های میتونن فریز بشن و وقتی دیگه بیشتر از این تعداد نتونیم ترد بسازیم،‌ برنامه فریز میشه. درسته که spring mvc در دسته sync/blocking هست ولی سرور هایی مثل تامکت، بصورت دیفالت ۲۰۰ ترد (یوزر) رو میتونه هندل کنه. پس جایگاه اصلی spring mvc در این دسته هست ج) asynchronous non-blocking : به این صورت که یک event loop ای موجود هست که تعداد محدودی ترد داره و این ترد ها همیشه مشغولن. تفاوتی که این مدل با بقیه داره در اینه که هر ترد دیگه منتظر جواب یا ریسپانس نمیمونه و کانتکس سویچ انجام میشه و ترد میره به یه کار دیگه میرسه. هر وقت هم نتیجه ریکوئست قبلی آماده بود،‌ با سیگنال onComplete برمیگرده. تعداد event loop ها وابسته به تعداد core ها متغیر هست. به این مدل reactive هم گفته میشود به این دلیل که برنامه به آماده شدن یا به ارور خوردن ریکوئست، واکنش نشون میده. spring webflux در این دسته قرار داره۲. مشخصات سیستم :cpu: core i3-9100fram : 16GBos : arch linux۳. تمام لود تست ها در لوکال انجام شده درنتیجه  latency نداریم۴. تاخیر یا latency در شبکه، مدت‌ زمانی است که طول می‌کشد تا یک درخواست از مبدا به مقصد ارسال شود۵. تست ها با ابزار jmeter گرفته شده اند۶. از اینجا به بعد تفاوت های مدل reactive و async/blocking که حالت کلی تری از خود spring mvc و spring webflux  هست رو بررسی میکنیمتعریف برنامه هادو برنامه spring boot ایجاد کرده ایم. یکی بصورت mvc پیاده شده و یکی هم بصورت reactive.این دو برنامه دقیقا عین هم نوشته شده اند و هر کدوم به دیتابیسی وصل میشن و اطلاعات رو مینویسن و میخونن.لینک پروژه mvcلینک پروژه reactiveلود تست بدون تاخیردر نمودار زیر لود تست این دوتا برنامه رو انجام داده ایم که از ۱۰ کاربر همزمان شروع کرده ایم تا ۳۰ هزار کاربر همزمان:نمودار لود تست بدون تاخیراحتمالا تا حالا انتظار داشتین که مدل reactive بهتر عمل کنه در مقابل async/blocking . اما واقعیت اینه که در مثال ما،‌ تا ۱۷۵۰۰ کاربر همزمان، مدل mvc  سرعت بهتری در پاسخگوئی به ریکوئست ها داره. اما این یک موضوع نسبیه. دلیل اینکه مدل reactive تا ۱۷۵۰۰ کاربر همزمان، سرعت پاسخگوئی کمتری داره اینه که context switch در event loop انجام میشه و باعث میشه به نسبت سرعت کمتری در این بازه داشته باشیم.اما این همه ماجرا نیست. در مدل async/blocking مشکل بدتری وجود داره اونم این که ریکوئست ها fail میشن ! تا ۵ هزار ریکوئست ما خطایی نداریم. در تست هایی که انجام دادم، وقتی ۱۰ هزار کاربر همزمان ریکوئست میزنن، درصدی از ریکوئست ها fail میشن (قاعدتا موقع بروز اولین خطا ها موقعی هست که تعداد کاربرا زیر ۱۰ هزار و بالای ۵ هزار هست) و این fail شدن ها،‌هرچقد تعداد کاربران افزایش پیدا کنه، بیشتر میشن، درحالیکه که توی مدل reactive اصلا fail شدن وجود نداره و درصد fail ها صفره !درصد شکست های مدل async/blockingلود تست با تاخیر یا دیلیتا حالا قدرت اصلی reactive رو ندیدیم. یه مثال در دنیای واقعی رو در نظر بگیرید. اینکه ممکنه شما در برنامه یا سرویستون به سرویس های دیگه یا api های دیگه ریکوئست بزنین و اطلاعاتی رو دریافت کنین. طبیعتا این کار به این معنی هست که زمانی بین وصل شدن، ارسال و دریافت اطلاعات به اون سرویس یا api وجود داره. همچنین ممکنه اون سرویس یا api هم به مشکل بخوره که تو این مثال ما این مشکلات سرویس های خارجی در نظر نگرفته ایم و صرفا دیلی رو درنظر گرفته ایم. گرچه مدل reactive هم راهکاری هایی برای ارور هندلینگ داره.در نمودار زیر لود تست این دوتا برنامه رو با ۱۰ هزار کاربر همزمان با دیلی هایی از ۱ تا ۵ ثانیه به ازای هر ریکوئست رو آورده ایم:نمودار لود تست با دیلی تعداد کاربران همزان: ۱۰ هزارنتایج شگفت انگیز نیست؟نمودار مدل reactive همزمان که دیلی هر ریکوئست بیشتر میشه، تقریبا ثابت هست. حتی وقتی که یک ثانیه دیلی داره،‌ با مثال قبلی که ۱۰ هزار کاربر همزمان بدون دیلی انجام شده،‌ زمان جواب دادن به ریکوئست یکیه.قدرت اصلی reactive در اینجاس. همزمان که به کاربرا در سریع ترین زمان ممکن جواب رو برمیگردونه، بدون شکست هم هست! درحالیکه مدل async/blocking  رفته رفته خطاهاش بیشتر و بیشتر میشه و باعث میشه انتخاب بدی تو این شرایط باشه‌ :درصد شکست های مدل async/blocking ستون آخر این نمودار به این معنیه که شما به ۱۷ درصد کاربراتون جواب نمیدید!نکته: وقتی delay وجود داره به این معنی نیست که ریکوئستی که حداقل ۵ ثانیه زمان میبره تا جواب بده، در مدل reactive کاهش پیدا کنه و بشه ۱ ثانیه. نه. به این معنی هست که میانگین زمان و حداکثر زمان هر ریکوئست ها در تعداد بالا رو کاهش بده. در مثال ما در مدل reactive، میانگین هر درخواست با ۵ ثانیه دیلی حدودا ۵٫۵ ثانیه هست در حالی که در مدل async/blocking این میانگین حدود ۱۰۶ ثانیه هست. به این معنی که یک کاربری ممکنه ۱۰۶ ثانیه منتظر بمونه تا درخواستش جواب داده بشه!چه مواقعی از کدوم استفاده کنیم؟از async/blocking زمانی استفاده کنید که مطمئنید تعداد کاربرانتون که همزمان میتونن درخواست به سرور شما بزنن، زیر ۱۰ هزار تا باشه (مقدار عددی رو خودتون باید توی vps یا سرور ابریتون حساب کنید) و api call ای وجود ندارهاز reactive زمانی استفاده کنید که پیشبینی میکنید، تعداد کابرانتون خیلی بیشتر از ۱۰ هزار تا باشه و مطمئنید که هر ریکوئست ممکنه دیلی داشته باشه. این دیلی میتونه api call یا عملیات های  io دیگری باشهخلاصهاز مجموع این مباحث به این نتایج میرسیم:در مدل async/blocking هر ریکوئست به معنی یک ترد هستدر مدل async/blocking چون به ازای هر ریکوئست یک ترد استفاده یا ایجاد میشه، درنتیجه مموری بیشتری استفاده میشهدر مدل async/blocking هر ترد ممکنه بلاک بشه در نتیجه cpu زمانی رو در انتظار میمونه و کاری نمیکنهدر مدل reactive تعداد محدودی ترد در thread pool که زمانبندی میشن، وجود دارن و در نتیجه، مموری کمتری استفاده میشه در مدل reactive چون این گپ انتظار رو از مدل async/blocking حذف کرده،‌ پس cpu کار بیشتری انجام میده و همیشه مشغول پردازش هست.منابع:کتاب reactive springکتاب modern java in actionprojectreactor.iodocs.spring.iostackoverflow.com&lt;br/&gt;هرجا سوال، پیشنهاد، اصلاحی داشتید ممنون میشم در کامنتا من و بقیه رو مطلع کنید ?</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Fri, 10 Jun 2022 16:47:48 +0430</pubDate>
            </item>
                    <item>
                <title>مقدمه ای بر تست نویسی در جاوا، Testcontainers قسمت ۴</title>
                <link>https://virgool.io/@beanvortex/%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-testcontainers-%D9%82%D8%B3%D9%85%D8%AA-%DB%B4-dmigmyckhz7g</link>
                <description>تا اینجای کار ما از دیتابیس h2  برای تست های یکپارچمون استفاده میکردیم که چون in memory هست،‌سرعت بالایی از استارت تا انجام دادن کوئری ها داره. اما این دیتابییس برای پروداکشن مناسب نیست. و اگه میخوایم که بهترین شبیه سازی پروداکشن رو در تست ها داشته باشیم بهتره از دیتابیس های واقعی استفاده کنیم. ممکنه ما یک کدی داشته باشیم که از یک ویژگی خاص مربوط به دیتابیس postgresql استفاده میکنه و دیتابیس h2 از این پشتیبانی نمیکنه. برای اینکار یک راه اینه که یه دیتابیس واقعی رو تو سیستم خودمون بسازیم و تنظیمات مربوطه ش رو انجام بدیم ولی ممکنه که بخواهید نوع دیتابیس رو به یکباره عوض کنید، که باید دیتابیس جدید رو نصب کنید تو سیستمون و دوباره کانفیگش کنید.کتابخونه Testcontainer به کمک ما اومده که این مراحل رو حذف کنیم و فقط با نوشتن نام و ورژن دیتابیس، دیتابیس مارو بالا بیاره تا بتونیم در تست ها ازش استفاده کنیمتست کانتینر همونطور که از اسمش میشه استنباط کرد، از داکر برای اینکار استفاده میکنه و حتما باید داکر در سیستمتون نصب باشهبرای پروژه این قسمت از پروژه قبلی استفاده خواهیم کرد فقط لازمه که این وابستگی هارو به پروژه اضافه کنیم:gradle:testImplementation &amp;quotorg.testcontainers:testcontainers:1.16.3&amp;quot
testImplementation &amp;quotorg.testcontainers:junit-jupiter:1.16.3&amp;quot
testImplementation &#039;org.testcontainers:postgresql:1.16.3&#039;maven:&lt;dependency&gt;
    &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
    &lt;artifactId&gt;testcontainers&lt;/artifactId&gt;
    &lt;version&gt;1.16.3&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
    &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;
    &lt;version&gt;1.16.3&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
    &lt;artifactId&gt;postgresql&lt;/artifactId&gt;
    &lt;version&gt;1.16.3&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;دپندسی آخری مربوط به دیتابیسی که قراره استفاده کنیم هست و میتونین برای دیتابیس های دیگه از خود سایت Testcontainers از تب مربوط به Modules/databases اقدام کنید.برای پیاده کردن تست کانتینر میخوایم که از اول با راه هایی موجود شروع کنیم و به یه نقطه ایده آل از پیاده سازی برسیم. در گیت برنچ هایی ایجاد کردم که میتونید تموم این روش ها رو به ترتیب ببنید و در نهایت پیاده سازی ایده آل رو در برنچ main قرار داده ام:ابتدا اول با استفاده از دستور زیر ایمیج دیتابیس مورد نظر رو از داکر دانلود کنید. برای مثال ما این هست:docker pull postgres:13.1-alpineبرنچ step1 :برای اینکه روند کلی رو در این پست نشون بدم فقط تغییرات رو در یک کلاس نشون میدیم و بقیه تست کلاس ها به عهده خودتون هست یا میتونید در برنچ مربوطه این تغییرات رو برای کلاس های دیگه ببینید. در تست کلاس  ProductControllerTest این موارد رو مینویسیم:...
@Testcontainers
class ProductControllerTest {
...
    @Container
    private final PostgreSQLContainer container = new PostgreSQLContainer(&amp;quotpostgres:13.1-alpine&amp;quot)
            .withDatabaseName(&amp;quottest&amp;quot)
            .withUsername(&amp;quotusername&amp;quot)
            .withPassword(&amp;quotpassword&amp;quot);
...
}اولین انوتیشنی که میبینیم Testcontainers هست که وظیفش اینه که فیلد هایی که درون کلاس با Container انوتیت شدن رو پیدا کنه و کانتینری که در این فیلد تعریف شده است رو اجرا کنه.در این فیلد یه کانتینر پوستگره ایجاد کردیم که داخل سازنده ش نام ایمیج و ورژنش رو مشخص کرده ایم. در ادامه بصورت استاتیک، نام دیتابیس، یوزرنیم و رمز دیتابیس رو به کانتکس اسپرینگ فهمونده ایماگه تست کلاس رو اجرا کنیم خیلی زمان میبره تا کل تست ها پس بشن و برای سیستم من حدود ۳۰ ثانیه هست. دلیلش این هست که یک کانتینر برای هر متد تست ایجاد و حذف میشه. این عمل سرعت رو پایین میاره.راه حل چیه؟ به فیلد کانتینر کلیدواژه static اضافه کنیم. در این حالت این کانتینر فقط یکبار اونم برای این کلاس ایجاد میشه و برای سیستم من حدود ۱۰ ثانیه طول میکشه. ولی بازم از حالت ایده آل فاصله داریم.برنچ step2 :حالا فرض کنیم که برای همه تست های پروژه این فیلد و انوتیشن ها رو اضافه کردیم. اگه کل تست های پروژه رو به یکباره اجرا کنیم، اتفاقی که میوفته، اینبار برای هر کلاس یک کانتینر ایجاد میشه و بعد تموم شدن تست های کلاس، کانتینر حذف میشه و برای کلاس تست بعدی دوباره این عمل انجام میشه. بازم اینکار ایده آل نیست و سرعت اجرای همه تست ها پایین میاد برای من بین ۲۵ تا ۴۰ ثانیه طول کشید. کاری که میکنیم به این صورت هست که تموم کد هایی که برای کانتینر نوشتیم به همراه  رو پاک میکنیم و یک کلاس در پکیج تست ها به این صورت ایجاد میکنیم و در همه کلاس های تست از این کلاس ارث بری میکنیم:public class DatabaseContainer {

    private static final PostgreSQLContainer container =
            (PostgreSQLContainer) new PostgreSQLContainer(&amp;quotpostgres:13.1-alpine&amp;quot)
                    .withReuse(true);

    // this script should be run for the first time
    // echo testcontainers.reuse.enable=true  &gt; ~/.testcontainers.properties

    static {
        container.start();
    }

    @DynamicPropertySource
    public static void overrideProps(DynamicPropertyRegistry registry) {
        registry.add(&amp;quotspring.datasource.url&amp;quot, container::getJdbcUrl);
        registry.add(&amp;quotspring.datasource.username&amp;quot, container::getUsername);
        registry.add(&amp;quotspring.datasource.password&amp;quot, container::getPassword);
        registry.add(&amp;quotspring.datasource.driver-class-name&amp;quot, container::getDriverClassName);
        registry.add(&amp;quotspring.jpa.properties.hibernate.dialect&amp;quot, () -&gt; &amp;quotorg.hibernate.dialect.PostgreSQLDialect&amp;quot);
    }
}همونطور که توی یک کامنت میبینید  اول این دستور رو اجرا کنید تا تست کانتینر بدونه که قراره از کانتینر دوباره استفاده کنه:echo testcontainers.reuse.enable=true  &gt; ~/.testcontainers.propertiesبرای ویندوز فایلی به این نام .testcontainers.properties که با نقطه شروع میشود در پوشه home یا همان c:/Users/username ایجاد کنید و مقدار زیر رو داخلش کپی کنید. بدیهی است که username در این مسیر نام کاربری خود شماست:testcontainers.reuse.enable=true در فیلدی که تعریف کرده ایم متد withReuse رو فراخونی کردیم که این اجازه رو به تست کانتینر میده تا هربار نیاد برای هر کلاس تست، کانتینر رو ایجاد و حذف کنه بلکه کانتینر رو نگه میداره و برای کلاس بعدی نیاز نیست که دوباره ایجاد بشه.در متدی که با انوتیشن DynamicPropertySource مشخص کرده ایم به اسپرینگ گفته ایم که در این متد پراپرتی های دیتابیس بصورت دایانامیک یا پویا اضافه میشن. چون تست کانتینر این پراپرتی ها مثل پورت رو رندوم جنریت میکنه تا تداخلی با پورت های دیگه سیستم نداشته باشه نیازی نیست که ما خودمون بصورت دستی این پراپرتی ها رو ست بکنیم.در متد بعدی هم گفتیم که قبل از  همه کلاس ها این کانتینر رو استارت کنه. دقت کنید که مشخص نکردیم بعد تموم شدن تست های پروژه این کانتینر حذف بشه. پس برای دفعه بعدی اجرای کل تست ها، چون کانتینر هنوز در حالت اجراس،‌ سرعت اجرا شدن تست ها بیشتر از قبل میشه.برنچ main :این روش ارث بری زیاد جالب نیس چون ممکنه بازم همچین کلاس هایی در تست ها داشته باشیم که نیاز باشه ارثبری کنیم. پس از اکستنشن های junit برای اینکار استفاده میکنیم. ابتدا کلاس DatabaseContainer به اینصورت بازنویسی میکنیم:public class DatabaseContainer implements BeforeAllCallback {

    private static final PostgreSQLContainer container =
            (PostgreSQLContainer) new PostgreSQLContainer(&amp;quotpostgres:13.1-alpine&amp;quot)
                    .withReuse(true);

    // this script should be run for the first time
    // echo testcontainers.reuse.enable=true  &gt; ~/.testcontainers.properties

    @DynamicPropertySource
    public static void overrideProps(DynamicPropertyRegistry registry) {
        registry.add(&amp;quotspring.datasource.url&amp;quot, container::getJdbcUrl);
        registry.add(&amp;quotspring.datasource.username&amp;quot, container::getUsername);
        registry.add(&amp;quotspring.datasource.password&amp;quot, container::getPassword);
        registry.add(&amp;quotspring.datasource.driver-class-name&amp;quot, container::getDriverClassName);
        registry.add(&amp;quotspring.jpa.properties.hibernate.dialect&amp;quot, () -&gt; &amp;quotorg.hibernate.dialect.PostgreSQLDialect&amp;quot);
    }

    @Override
    public void beforeAll(ExtensionContext context) {
        container.start();
    }
}بعد برای هر کلاس تست این انوتیشن رو اضافه میکنیم و ارثبری رو حذف میکنیم:@ExtendWith(DatabaseContainer.class)تا اینجای کار ما قدم به قدم تست کانتینر رو ایجاد و تنظیم کردیم تا از اجرای چند دقیقه ای همه تست ها به زیر ده ثانیه رسیدیم.تست کانیتر قابلیت های زیادی داره، میشه حتی در حالت پروداکشن هم استفاده کرد یا ایمیج هایی مثل rabbitMQ رو هم در کلاس های تست داشته باشیملینک پروژه: LINKانشاالله اگه فرصتی باقی باشه در پست های بعدی درمورد تست کردن بصورت Reactive هم صحبت خواهیم کرد. هرجا سوال، پیشنهاد، اصلاحی داشتید ممنون میشم در کامنتا من و بقیه رو مطلع کنید ?</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Thu, 17 Mar 2022 10:31:05 +0330</pubDate>
            </item>
                    <item>
                <title>مقدمه ای بر تست نویسی در جاوا، integration قسمت ۳</title>
                <link>https://virgool.io/@beanvortex/%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-integration-%D9%82%D8%B3%D9%85%D8%AA-%DB%B3-swupnfpcwvcv</link>
                <description>در تست نویسی یکپارچه کامپوننت ها و قسمت های مختلف برنامه رو یکجا در یک گروه تست میکنیم.همونطور که از این تصویر مشخص هست این نوع تست بعد از تست واحد قرار میگیرد. طبیعتا چون با کامپوننت های زیادتری نسبت به تست نویسی واحد سر و کار داریم، هزینه اجرا و سرعت اجرا هم تغییر میکنند.در اسپرینگ این هزینه و سرعت در لود شدن و بالا آمدن اپلیکیشن در هر کلاس تست دیده میشود چون بهرحال کانتکس اسپرینگ برای ما کامپوننت ها و bean های مختلف رو فراهم میکنه برخلاف mock کردن که در قسمت قبل دیدیم. البته از ماک میشه تو تست های یکپارچه هم استفاده کرد مثلا اگه از spring security استفاده میکنیم میتونیم آبجکت های این دپندسی مثل Authentication رو ماک کنیمهدف ما در این قسمت اینه که:۱- لایه repository رو تست کنیم۲- لایه service و repository رو تست کنیم۳- لایه service و repository و controller رو تست کنیمبرای شروع به این سایت میریم و پروژه رو با این شرایط و وابستگی ها ایجاد میکنیم:پروژه رو دانلود میکنیم و این کلاس هارو ایجاد میکنیم:Entity:import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    public Product(String name, String description, BigDecimal price) {
        this.name = name;
        this.description = description;
        this.price = price;
    }
}Repository:import ir.darkdeveloper.integration.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ProductRepo extends JpaRepository&lt;Product, Long&gt; {
    @Query(&amp;quotselect p from Product p &amp;quot +
            &amp;quotwhere upper(p.name) like upper(concat(&#039;%&#039;, :str, &#039;%&#039;)) &amp;quot +
            &amp;quotor upper(p.description) like upper(concat(&#039;%&#039;, :str, &#039;%&#039;)) &amp;quot)
    List&lt;Product&gt; findAllByNameAndDescriptionContainsIgnoreCase(String str);
}Service:import ir.darkdeveloper.integration.model.Product;
import ir.darkdeveloper.integration.repo.ProductRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepo repo;

    public Product save(Product product) {
        return repo.save(product);
    }
    public List&lt;Product&gt; saveAll(List&lt;Product&gt; products) {
        return repo.saveAll(products);
    }
    public List&lt;Product&gt; getAll() {
        return repo.findAll();
    }
    public Product getById(Long Id) {
        return repo.findById(Id).orElse(null);
    }
    public List&lt;Product&gt; getByNameAndDescriptionContainsIgnoreCase(String str) {
        return repo.findByNameAndDescriptionContainsIgnoreCase(str);
    }
    public boolean deleteById(Long Id) {
        try {
            repo.deleteById(Id);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    public void deleteAll() {
        repo.deleteAll();
    }
}Controller:import ir.darkdeveloper.integration.model.Product;
import ir.darkdeveloper.integration.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping(&amp;quot/api/v1/products&amp;quot)
public class ProductController {
    private final ProductService service;
    
    @PostMapping(&amp;quot/save/&amp;quot)
    public ResponseEntity&lt;Product&gt; saveProduct(@RequestBody Product product) {
        return ResponseEntity.ok(service.save(product));
    }
    @GetMapping(&amp;quot/{id}/&amp;quot)
    public ResponseEntity&lt;Product&gt; getProduct(@PathVariable Long id) {
        return ResponseEntity.ok(service.getById(id));
    }
    @GetMapping(&amp;quot/&amp;quot)
    public ResponseEntity&lt;List&lt;Product&gt;&gt; getAllProducts() {
        return ResponseEntity.ok(service.getAll());
    }
    @GetMapping(&amp;quot/search/&amp;quot)
    public ResponseEntity&lt;List&lt;Product&gt;&gt; searchProducts(@RequestParam(&amp;quotstr&amp;quot) String str) {
        return ResponseEntity.ok(service.getByNameAndDescriptionContainsIgnoreCase(str));
    }
}و تنظیمات دیتابیس در فایل application.yml (اگر وجود ندارد در پوشه resources ایجاد کنید و فایل قبلی رو حذف کنید)spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:db
    username: sa
    password: saدر پروژه های واقعی حتما از دیتابیس هایی مثل postgresql یا oracle یا mysql استفاده کنید و برای تست کردن از دیتابیس h2 استفاده کنید. برای اینکه کانفیگ دیتابیس تست یا کانفیگ دیتابیس اصلی جدا کنید، یک همچین فایلی رو در مسیر test/resources ایجاد کنید. اسپرینگ از این کانفیگ برای تست ها استفاده خواهد کردتست لایه Repository :همونطور که قبلا گفتیم این لایه خیلی از متد های پیشفرضش تست شده هستند و مشکلی ندارند. چیزی که ما تست میکنیم کوئری های کاستومی هستند که بسته به نیازمون در پروژه مینویسیم و لازمه که عملکرد این ها رو تست کنیم. در این مثال بخصوص ما متد findByNameAndDescriptionContainsIgnoreCase توی ریپازیتوری با یک کوئری کاستوم داریم که اومده در نام و توضیحات این  product ها جستجو میکنه و لیست محصولاتی که  نام و توضیحاتشون شامل این متن هست رو برمیگردونه.توضیح کوئری هم به این صورت هست که محصولاتی رو انتخاب میکنه که این متن که بهش میدیم  یا باید شامل نام یا توضیحات محصول بدون توجه به بزرگی و کوچکی حروف باشد .چون ما اومدیم یک کوئری کاستوم ساختیم باید تست این رو بنویسیم. برای این اینترفیس یک کلاس تست ایجاد میکنیم و تست ها رو با حالت های مختلف ممکن مینویسیم:import ir.darkdeveloper.integration.model.Product;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
class ProductRepoTest {
    private final ProductRepo productRepo;
    
    @Autowired
    ProductRepoTest(ProductRepo productRepo) {
        this.productRepo = productRepo;
    }
    
    @BeforeEach
    void saveProducts() {
        var products = List.of(
                new Product(&amp;quotsome_name_Abc&amp;quot, &amp;quotDesCriPtiOn&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotNaMeNumber2&amp;quot, &amp;quotSome_description25&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotA Name For Product&amp;quot, &amp;quotContains Description About Product&amp;quot,
                        new BigDecimal(&amp;quot136.54&amp;quot)),
                new Product(&amp;quotنام محصول&amp;quot, &amp;quotتوضیحات محصول&amp;quot, new BigDecimal(&amp;quot136.54&amp;quot))
        );
        productRepo.saveAll(products);
    }

    @AfterEach
    void deleteAll() {
        productRepo.deleteAll();
    }

    @Test
    void findByNameAndDescriptionContains1() {
       var products = productRepo.findByNameAndDescriptionContainsIgnoreCase(&amp;quotname&amp;quot);
        assertEquals(3, products.size());
    }

    @Test
    void findByNameAndDescriptionContains2() {
        var products = productRepo.findByNameAndDescriptionContainsIgnoreCase(&amp;quotdEscRiptiOn&amp;quot);
        assertEquals(3, products.size());
    }

    @Test
    void findByNameAndDescriptionContains3() {
        var products = productRepo.findByNameAndDescriptionContainsIgnoreCase(&amp;quotنام&amp;quot);
        assertEquals(1, products.size());
    }
}ما کلاس رو با انوتیشن DataJpaTest نوشتیم این انوتیشن اپلیکیشن رو بالا میاره و کانتینر ioc رو آماده میکنه لذا ما میتونیم ریپازیتوری رو در کلاس تست تزریق کنیم. بعد ما هر بار قبل از اجرای تست ها، اطلاعات چند تا محصول رو در دیتابیس ذخیره میکنیم و بعد از تموم شدن هر تست این دیتا هارو حذف میکنیم تا برای تست بعدی به اصطلاح یک fresh state داشته باشیم. البته نوشتن این afterEach الزامی نیست چون توی انوتیشن DataJpaTest از انوتیشن Transactional استفاده شده و تمام دیتا هایی که ساخته میشه رو رول بک میشه  و برای تست بعدی ذخیره نمیشه. در سه تست بعدی ما اومدیم حالت های مختلف جستجو رو نوشتیم که ببینیم کوئری ما درست کار میکنه یا نه. مثلا حروف رو بزرگ و کوچک نوشتیم که ببینیم کوئری این بزرگی و کوچکی رو نادیده میگیره یا نه یا مثلا عباراتی رو نوشتیم که ببینیم اون عبارت or در کوئری درست کار میکنه یا نهتست لایه Service : برای این لایه یک راه اینه که ریپازیتوری رو ماک کنیم که توصیه میشه. یک راه دیگه اینم که بصورت integration تست کنیم و لایه ریپازیتوری رو هم درگیر کنیم تا بتونیم عملکرد هایی مثل save و update و delete رو هم تست کنیم. برای اینکار لازمه که کلاس تستی بسازیم که بالایش انوتیشن SpringBootTest باشد. این انوتیشن میاد و کانتکس و کانتینر ioc اسپرینگ رو بالا میاره و عملا یکبار اپلیکیشن رو به ازای هر کلاس تست ران میکنه. بر خلاف DataJpaTest این انوتیشن دیتا های سیو شده رو رول بک نمیکنه که میتونیم دو کار انجام بدیم:۱ -  قبل و بعد هر تست دیتا رو بصورت تازه و دست نخورده آماده کنیم۲ - تست هارو بترتیب از سیو شدن  تا دیلیت شدن بنویسیم که سرعت اجرای بیشتری داره نسبت به اولیبسته به نیاز و صلاح دیدتون میتونین یک روش رو انتخاب کنین که هر دو روش رو بترتیب اینجا پیاده کرده ایم:import ir.darkdeveloper.integration.model.Product;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
public class ProductServiceNoOrderTest {
    private final ProductService service;
    private List&lt;Long&gt; productIds;

    @Autowired
    public ProductServiceNoOrderTest(ProductService service) {
        this.service = service;
    }

    @BeforeEach
    public void setUp() {
        service.deleteAll();
        var products = List.of(
                new Product(&amp;quotsome_name_Abc&amp;quot, &amp;quotDesCriPtiOn&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotNaMeNumber2&amp;quot, &amp;quotSome_description25&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotA Name For Product&amp;quot, &amp;quotContains Description About Product&amp;quot,
                        new BigDecimal(&amp;quot136.54&amp;quot)),
                new Product(&amp;quotنام محصول&amp;quot, &amp;quotتوضیحات محصول&amp;quot, new BigDecimal(&amp;quot136.54&amp;quot))
        );
        service.saveAll(products);
        productIds = new ArrayList&lt;&gt;();
        products.forEach(product -&gt; productIds.add(product.getId()));
    }

    @Test
    void getAll() {
        var fetchedProducts = service.getAll();
        assertThat(fetchedProducts).isNotEmpty();
        assertThat(fetchedProducts.size()).isEqualTo(4);
        IntStream.range(0, fetchedProducts.size()).forEach(i -&gt;
                assertThat(fetchedProducts.get(i).getId()).isEqualTo(productIds.get(i))
        );
    }

    @Test
    void getById() {
        var fetchedProduct = service.getById(productIds.get(0));
        assertThat(fetchedProduct).isNotNull();
        assertThat(fetchedProduct.getId()).isEqualTo(productIds.get(0));
    }

    @Test
    void getByNameAndDescriptionContainsIgnoreCase() {
        var fetchProducts = service.getByNameAndDescriptionContainsIgnoreCase(&amp;quotabc&amp;quot);
        assertThat(fetchProducts).isNotEmpty();
        assertThat(fetchProducts.size()).isEqualTo(1);
    }

    @Test()
    void deleteById() {
        var deletedProduct = service.deleteById(productIds.get(0));
        assertThat(deletedProduct).isTrue();
    }
}همونطور که مشخص هست هر بار قبل از هر تست یکبار تیبل محصول رو پاک میکنیم و دیتاهارو دوباره وارد میکنیم و دوباره آیدی هاشون رو ثبت میکنیم. در تست ها نیز متد های سرویس رو تست میکنیم.اسپرینگ خودش یک انوتیشنی با عنوان DirtiesContextداره که برای تمیز کردن دیتاهای دیتابیس کاربرد داره که هم برای کلاس میشه اعمال کرد هم برای متد. برای کلاس به اینصورت کار میکنه که اگه همه تست کلاس هارو یکجا ران کنید،‌ دیتا هایی که در یک تست کلاس ایجاد شده در تست کلاس بعدی وجود نخواهد داشت. و  برای متد به این صورته که دیتاهایی که در در هر تست متد ایجاد شده اند برای تست متد های بعدی وجود نخواهند داشت. دو تا اول مربوط به کلاس هست و دو تای بعدی مربوط به متد و دوتا آخر روی کلاس نوشته میشوند و قبل و بعد هر متد اعمال میشن :@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) // default

@DirtiesContext(methodMode = DirtiesContext.MethodMode..BEFORE_METHOD)
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) // default

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)گاهی اوقات ممکنه تست کلاس ها به قدری سریع اجرا بشن که حالت دیفالت عمل نکنه. پس بهتره همه کلاس هایی که لازمه رو به حالت before_class  تنظیم کنید. روش دوم به این صورت هست :import ir.darkdeveloper.integration.model.Product;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ProductServiceInorderTest {

    private final ProductService service;
    private static final List&lt;Long&gt; productIds = new ArrayList&lt;&gt;();

    @Autowired
    public ProductServiceInorderTest(ProductService service) {
        this.service = service;
    }

    @Test
    @Order(1)
    void saveAll() {
        var products = List.of(
                new Product(&amp;quotsome_name_Abc&amp;quot, &amp;quotDesCriPtiOn&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotNaMeNumber2&amp;quot, &amp;quotSome_description25&amp;quot, new BigDecimal(1)),
                new Product(&amp;quotA Name For Product&amp;quot, &amp;quotContains Description About Product&amp;quot,
                        new BigDecimal(&amp;quot136.54&amp;quot)),
                new Product(&amp;quotنام محصول&amp;quot, &amp;quotتوضیحات محصول&amp;quot, new BigDecimal(&amp;quot136.54&amp;quot))
        );
        var savedProducts = service.saveAll(products);
        assertNotNull(savedProducts);
        assertThat(savedProducts).isNotEmpty();
        products.forEach(product -&gt; productIds.add(product.getId()));
    }

    @Test
    @Order(2)
    void getAll() {
        var fetchedProducts = service.getAll();
        assertThat(fetchedProducts).isNotEmpty();
        assertThat(fetchedProducts.size()).isEqualTo(4);
        IntStream.range(0, fetchedProducts.size()).forEach(i -&gt;
                assertThat(fetchedProducts.get(i).getId()).isEqualTo(productIds.get(i))
        );
    }

    @Test
    @Order(3)
    void getById() {
        var fetchedProduct = service.getById(productIds.get(0));
        assertThat(fetchedProduct).isNotNull();
        assertThat(fetchedProduct.getId()).isEqualTo(productIds.get(0));
    }

    @Test
    @Order(4)
    void getByNameAndDescriptionContainsIgnoreCase() {
        var fetchProducts = service.getByNameAndDescriptionContainsIgnoreCase(&amp;quotabc&amp;quot);
        assertThat(fetchProducts).isNotEmpty();
        assertThat(fetchProducts.size()).isEqualTo(1);
    }

    @RepeatedTest(4)
    @Order(5)
    void deleteById() {
        var deletedProduct = service.deleteById(productIds.get(0));
        productIds.remove(0);
        assertThat(deletedProduct).isTrue();
    }
}که کلاس رو با انوتیشن TestMethodOrder(MethodOrderer.OrderAnnotation.class) مینویسم  و هر متد تست را به انوتیشن Order و شماره ای که ترتیب آنها رو نشون میده مینویسیم (به ایمپورت های این انوتیشن ها دقت کنید)یک انوتیشن دیگری به نام RepeatedTest داریم که یک عددی میگیره و این تست رو به این تعداد اجرا میکنهتست لایه Controller :توی این مدل تست میخواهیم با یک تیر دو نشون بزنیم. به این صورت که تست های کنترلر رو مینویسیم و با هربار اجرای موفق، داکیومنت هایی برای rest api هامون بصورت خودکار جنریت میشه. تست لایه کنترلر بصورت یکپارچه هست. هدفی که داریم اینه که مثل دنیای واقعی یک http ریکوئست به کنترلمون بزنیم و دیتا رد و بدل کنیم. قبلا شاید از ابزاری مثل postman استفاده میکردید که برای خود من طاقت فرسا بود مثلا مواقعی بود که باید یکبار لاگین میکردن بعد توکن رو به ریکوئست بعدی میدادم و یک چیزی رو سیو میکردم بعد ... همه این مراحل رو دستی انجام میدادم که یک فانکشنالیتی ساده رو تست کنم. اما دیگه رفتم سراغ تست و الان به ندرت از پست من استفاده میکنم :import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ir.darkdeveloper.integration.model.Product;
import org.json.JSONObject;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.math.BigDecimal;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;


@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureRestDocs(&amp;quotdocs/product&amp;quot)
class ProductControllerTest {

    private final WebApplicationContext webApplicationContext;
    private final RestDocumentationContextProvider restDocumentation;
    private MockMvc mockMvc;
    private static Long productId;

    @Autowired
    ProductControllerTest(WebApplicationContext webApplicationContext,
                          RestDocumentationContextProvider restDocumentation) {
        this.webApplicationContext = webApplicationContext;
        this.restDocumentation = restDocumentation;
    }

    @BeforeEach
    void setupMockMvc() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(document(&amp;quot{methodName}&amp;quot))
                .build();
    }

    @Test
    @Order(1)
    void saveProduct() throws Exception {
        var product = Product.builder()
                .name(&amp;quotproductName&amp;quot)
                .description(&amp;quotproductDescription&amp;quot)
                .price(BigDecimal.valueOf(102.5))
                .build();

        mockMvc.perform(post(&amp;quot/api/v1/products/save/&amp;quot)
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .content(mapToJson(product))
                )
                .andDo(print())
                .andExpect(status().isOk())
                 // isMap means is it an object?
                .andExpect(jsonPath(&amp;quot$&amp;quot).isMap())
                .andExpect(jsonPath(&amp;quot$.name&amp;quot).value(&amp;quotproductName&amp;quot))
                .andExpect(jsonPath(&amp;quot$.description&amp;quot).value(&amp;quotproductDescription&amp;quot))
                .andExpect(jsonPath(&amp;quot$.price&amp;quot).value(BigDecimal.valueOf(102.5)))
                .andDo(result -&gt; {
                    var object = new JSONObject(result.getResponse().getContentAsString());
                    // your assertions here
                    productId = object.getLong(&amp;quotid&amp;quot);
                });

    }

    @Test
    @Order(2)
    void getProduct() throws Exception {
        mockMvc.perform(get(&amp;quot/api/v1/products/{id}/&amp;quot, productId)
                        .accept(MediaType.APPLICATION_JSON)
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath(&amp;quot$&amp;quot).isMap())
                .andExpect(jsonPath(&amp;quot$.id&amp;quot).value(productId))
                .andExpect(jsonPath(&amp;quot$.name&amp;quot).value(&amp;quotproductName&amp;quot))
                .andExpect(jsonPath(&amp;quot$.description&amp;quot).value(&amp;quotproductDescription&amp;quot))
                .andExpect(jsonPath(&amp;quot$.price&amp;quot).value(BigDecimal.valueOf(102.5)));
    }

    @Test
    @Order(3)
    void getAllProducts() throws Exception {
        mockMvc.perform(get(&amp;quot/api/v1/products/&amp;quot)
                        .accept(MediaType.APPLICATION_JSON)
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath(&amp;quot$&amp;quot).isArray())
                .andExpect(jsonPath(&amp;quot$.[0].id&amp;quot).value(productId))
                .andExpect(jsonPath(&amp;quot$.[0].name&amp;quot).value(&amp;quotproductName&amp;quot))
                .andExpect(jsonPath(&amp;quot$.[0].description&amp;quot).value(&amp;quotproductDescription&amp;quot))
                .andExpect(jsonPath(&amp;quot$.[0].price&amp;quot).value(BigDecimal.valueOf(102.5)));
    }

    @Test
    @Order(4)
    void searchProducts() throws Exception {
        mockMvc.perform(get(&amp;quot/api/v1/products/search/&amp;quot)
                        .accept(MediaType.APPLICATION_JSON)
                        .param(&amp;quotstr&amp;quot, &amp;quotdescript&amp;quot)
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath(&amp;quot$&amp;quot).isArray())
                .andExpect(jsonPath(&amp;quot$.[0].id&amp;quot).value(productId))
                .andExpect(jsonPath(&amp;quot$.[0].name&amp;quot).value(&amp;quotproductName&amp;quot))
                .andExpect(jsonPath(&amp;quot$.[0].description&amp;quot).value(&amp;quotproductDescription&amp;quot))
                .andExpect(jsonPath(&amp;quot$.[0].price&amp;quot).value(BigDecimal.valueOf(102.5)));
    }

    private String mapToJson(Object obj) throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(obj);
    }
}در ظاهر میدونم یزرع بزرگ به نظر میاد اما مطمن باشید خیلی هم سخت نیس و خود من از این تست ها بیشتر خوشم میاد.خب در مرحله اول یک تست یکپارچه داریم که با springBootTest مشخصش کردیم. بعد تست هارو بصورت ترتیبی اجرا میکنیم.و انوتیشینی که دپندسی restdocs رو برای ما آماده میکنه و پوشه ای که قراره داکیومنت هامون در اونجا قرار بگیره رو مشخص کردیم(این پوشه از روت پروژه ایجاد میشه)دو تا bean لازم داریم برای کانفیگ کردن MockMvc یکیش برای آماده سازی کانتکس وب هست یکیش هم برای rest docs .یک آبجکت MockMvc داریم که در متد setupMockMvc هر بار قبل از هر تست مقدار دهی میکنیم.و  یک مقدار لانگ که قراره آیدی این محصول رو برای ما نگه داره.در تست اول یک آبجکت پروداکت ساختیم و مقدار دهیش کردیم. در خط بعدی با mockMvc اومدیم و یک ریکوئست post ایجاد کردیم و uri ای که قراره استفاده بشه رو وارد کردیم. در دو خط بعدی محتوای ارسالی و محتوای دریافتی رو از نوع JSON قرار داده ایم. در متد content اومدیم یک متد دیگه ای که در همین کلاس تعریف شده ایجاد کردیم و کارش اینه که آبجکتی رو بگیره و به استرینگ json تبدیل کنه.در این حال ریکوئست ما تکمیل شد. اما چطور محتوای دریافتی یا ریسپانس رو تست کنیم؟در ادامه همین متد perform متد هایی مثل andDo و andExpect داریم که اولی همونطور که از اسمش مشخص است برای انجام دادن کاری روی محتوای ریسپانس هست که اومدیم و گفتیم این ریسپانس رو چاپ کنه. خروجی در کنسول همچین چیزی هست:متد بعدی andExpect هست که ما انتظاراتمون رو از ریسپانس دریافتی در این متد مشخص میکنیم مثلا در اولین مورد انتظار داریم که وضعیت ریسپانس ۲۰۰ یا اوکی باشد.در مورد های بعدی از متد jsonPath استفاده کردیم و میخواهیم بصورت مستقیم و جاوااسکریپ طور ، مقادیر json ای که در ریسپانس برگردانده شده هست رو بررسی و تحلیل کنیم. این متد یک رشته میگیرد که شروعش با علامت دلار هست و با نقطه به داخل json میرود. در ادامه با یک andDo دیگر اومدیم ریسپانس رو گرفتیم و آیدی رو از اون استخراج کردیم و در متغیری که بصورت گلوبال تعریف کرده بودیم ریخته ایم.بقیه تست ها هم شبیه این موردی که توضیح دادم هستند فقط یک مورد دیگر که لازمه توضیح بدم در آخرین تست یعنی searchProducts هست که اینجا چون مقدار بازگشتی ما یک json ای هست که با لیست شروع شده است، برای دسترسی به اعضای این لیست از علامت دسترسی به آرایه استفاده کردیم. هرجا که ریسپانس به یک لیست برسد باید از این علامت استفاده کنیم.لینک پروژه: LINKدر پایان مثل همیشه نمیشه خیلی از کارایی که با  mock mvc میشه کرد رو در اینجا تشریح کرد و بطور کامل توضیح داد. بهترین کار خوندن داکیومنت ها و  best practice های mock mvc هست. هرجا سوال، پیشنهاد، اصلاحی داشتید ممنون میشم در کامنتا من و بقیه رو مطلع کنید ?</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Thu, 17 Mar 2022 10:29:55 +0330</pubDate>
            </item>
                    <item>
                <title>مقدمه ای بر تست نویسی در جاوا، mockito قسمت ۲</title>
                <link>https://virgool.io/@beanvortex/%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-mockito-%D9%82%D8%B3%D9%85%D8%AA-%DB%B2-qfnd7mu0wq0v</link>
                <description>ماکیتو یک فریمورک ماک هست که mock در معنی لغوی میتوان به &quot; تقلید کردن &quot; رسید. در اصطلاح فنی وقتی ما میخواهیم متدی را تست کنیم که‌ این متد ممکنه به آبجکت های خارجی وابسته باشه که این آبجکت ها در هنگام اجرای اپلیکیشن فراهم میشن، مثلا یک متدی که با دیتابیس ارتباط میگیره. کار ماک کردن به اینصورت هست که یک آبجکت در اصطلاح dummy یا بی خاصیت ایجاد میکنه که ما بتونیم این وابستگی رو برای متد در حالت ایزوله فراهم کنیم که بتوانیم برای این متد تست واحد بنویسیم. برای مثال این قسمت، اول یک پروژه spring ایجاد میکنیم و معماری MVC رو پیاده میکنیم و سعی میکنیم لایه service این اپ رو تست کنیم. لایه سرویس ما چون وابسته به لایه repository هست و برای تست لایه سرویس لازم هست که context اسپرینگ لود بشه و آبجکت های لایه ریپازیتوری رو برامون فراهم کنه. اما ماکیتو به کمک ما میاد که دیگه نیایم کانتکس رو لود کنیم تا آبجکت ها برامون خودکار ساخته بشن، بلکه ما خودمون بوسیله این فریمورک میایم و آبجکت هامونو خودمون بصورت ایزوله و بدون لود کردن کانتکس اسپرینگ ایجاد میکنیم و استفاده میکنیم. مزیت اینکار سرعت بالای اجرای تست ها در مقایسه با تست یکپارچه هستپروژه ای که ایجاد میکنیم،‌به ۳ دپندنسی نیاز داریم:1- Spring web2- Lombok3- H2 database4- Spring data jpagradle: implementation &#039;org.springframework.boot:spring-boot-starter-web&#039;
implementation &#039;org.springframework.boot:spring-boot-starter-data-jpa&#039;
compileOnly &#039;org.projectlombok:lombok&#039;
runtimeOnly &#039;com.h2database:h2&#039;
annotationProcessor &#039;org.projectlombok:lombok&#039;
testImplementation &#039;org.springframework.boot:spring-boot-starter-test&#039;تنظیمات دیتابیس رو توی فایل application.properties یا application.yml انجام میدیم:(به فاصله ها دقت کنید)نکته: تنظیمات دیتابیس فقط برای این هست که اپلیکیشن در حالت عادی اجرا بشه.spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:db
    username: sa
    password: saلایه مدل:import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
}لایه ریپازیتوری:import ir.darkdeveloper.mocking.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepo extends JpaRepository&lt;Product, Long&gt; {
}و در آخر لایه سرویس رو که قراره تست کنیم:import ir.darkdeveloper.mocking.model.Product;
import ir.darkdeveloper.mocking.repo.ProductRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepo repo;

    public List&lt;Product&gt; getAll() {
        // your logic goes here
        // and this logic is going to be tested
        return repo.findAll();
    }
    public Product getById(Long Id){
        // your logic goes here
        // and this logic is going to be tested
        return repo.findById(Id).orElse(null);
    }
}برای تست این لایه یک کلاس تست در مسیری test ایجاد میکنیم:import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.when;

class ProductServiceTest {
    @Test
    void getAll() {
    }
    @Test
    void getById() {
    }
}هدف این هست که ما لایه سرویس رو در این تست ایزوله کنیم یعنی تمرکز اصلی تست رو روی این لایه بذاریم برای اینکار نیازه که وابستگی های این سرویس رو mock کنیم. در حال حاضر ما توی سرویس مون فقط یک وابستگی اونم به ریپازیتوری داریم که نیازه که ماک بشه. برای این منظور لازمه که بالای کلاس تست، این انوتیشین رو بنویسیم:@ExtendWith(MockitoExtension.class)و ریپازیتوری رو به این صورت ازش آبجکت ماک میسازیم:@Mock
private ProductRepo repo;در نهایت همچین کدی خواهیم داشت:@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

    @Mock
    private ProductRepo repo;
    private ProductService service;

    @BeforeEach
    void setUp() {
        service = new ProductService(repo);
    }

    @Test
    void getAll() {
    }
    @Test
    void getById() {
    }
}ما اومدیم یک آبجکت هم از productService ساخته ایم و توی متد setUp که با انوتیشن BeforeEach مشخص شده است، هربار قبل از اجرای متد های تست،‌ productSerive رو با ریپازیتوری ماک شده، مقداردهی میکنیم.برای تست کردن متد findAll همچین کدی رو پیاده کرده ایم:@Test
void getAll() {
    // given
    var given = List.of(
            new Product(1L, &amp;quotname&amp;quot, &amp;quotdescription&amp;quot, new BigDecimal(1)),
            new Product(2L, &amp;quotname2&amp;quot, &amp;quotdescription2&amp;quot, new BigDecimal(1)),
            new Product(2L, &amp;quotname3&amp;quot, &amp;quotdescription3&amp;quot, new BigDecimal(&amp;quot136.54&amp;quot))
    );
    // when
    when(repo.findAll()).thenReturn(given);

    // then
    var products = service.getAll();
    assertThat(products.size()).isEqualTo(3);
}در بخشی که با کامنت given نشون داده ایم دیتایی که قراره repository برگردونه رو مشخص کردیم.در بخش when این موضوع رو مشخص کرده ایم که وقتی متد findAll از ریپازیتوری فراخونی شد، چه چیزی رو برگردونه. در این بخش هست که ما لایه سرویس رو ایزوله کردیم و نیاز نیست که دیتابیس رو ران کنیم و مقادیر رو وارد کنیم و ...و در بخش then میایم و متدی که قراره توی لایه سرویس تست بشه رو فراخوانی میکنیم و بررسی های لازم رو روی مقدار بازگشتی از این متد رو بررسی میکنیم.در واقع در این پروسه ما اومدیم فقط تمرکزمون رو روی تست کردن بخش لاجیک لایه سرویس قرار دادیم و کاری با درست کار کردن لایه ریپازیتوری نداریم (تمام متد های پیشفرض لایه ریپازیتوری همگی ایمن هستند چون این متد هارو تیم spring فراهم کردن و همشون تست شده اند، اما برای کوئری های کاستوم لازمه که تست هایی نوشته بشود که بعدا در بخش دیگه ای بررسی خواهیم کرد)برای تست دوم هم همچین کدی داریم:@Test
void getById() {
    // given 
    var given = new Product(1L, &amp;quotname&amp;quot, &amp;quotdescription&amp;quot, new BigDecimal(1));
    // when
    when(repo.findById(1L)).thenReturn(Optional.of(given));
    // then
    var product = service.getById(1L);
    assertThat(product.getId()).isEqualTo(1L);
    assertThat(product.getName()).isEqualTo(&amp;quotname&amp;quot);
    assertThat(product.getDescription()).isEqualTo(&amp;quotdescription&amp;quot);
}که بازم در اینجا بخش given رو داریم که دیتا رو آماده میکنیمدر بخش when این دیتا رو به این متد ریپازیتوری دادیمو در بخش then متدی که قراره تست بشه رو فراخونی میکنیم و مقدار بازگشتی اون رو بررسی میکنیمکاری که ما توی بخش when میکنیم اصطلاحا بهش میگن stubbingلینک این پروژه: LINKاین بخش به پایان رسید گرچه خیلی مسائل باقی است که خودتون میتونید جاواداک های عالی ماکیتو رو بخونید و ازش در تست هاتون استفاده. اگر نظری، پیشنهادی،‌ اصلاحی دارید، لطفا در کامنت ها من و بقیه رو مطلع کنید ?</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Thu, 17 Mar 2022 10:28:05 +0330</pubDate>
            </item>
                    <item>
                <title>مقدمه ای بر تست نویسی در جاوا قسمت ۱</title>
                <link>https://virgool.io/@beanvortex/%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7-%D9%82%D8%B3%D9%85%D8%AA-%DB%B1-vfu9sqgwdwau</link>
                <description>تست نرم افزار و اجزای مختلف آن جز مهم ترین کارهایی هست که هر برنامه نویس باید بداند و توی پروژه هایش اعمال کند.بنام خدا در این سری تست نویسی قراره چند تا از best practice های تست نویسی در جاوا و spring رو با هم مرور کنیم. پس اگر این سری براتون مفید بود ممنون میشم که پست هارو لایک کنید و به اشتراک بگذاریدشاید اولش مثل من بگین که خب من که میدونم برنامم چطور کار میکنه، میدونم که فلان متد چه مقداری میگیره و چه مقداری برمیگردونه دیگه نیازی نیست خودمو به زحمت بندازم و بیام براش یه عالمه کد دیگه بنویسم که رفتار کد اصلیم رو تحلیل و بررسی کنه.در واقع وقتی برنامه شما به حالت پروداکشن میره،‌ ورودی هایی ممکنه به برنامه داده بشه که شما این ورودی ها رو پیش بینی نکرده بودین و فرض رو بر این گذاشته بودین که همیشه ورودی های درست به برنامه شما بیاد. یا اگرم پیش بینی کرده باشین، در نهایت با یه خطای نامفهوم به کاربر ریسپانس رو برمیگردانید که ممکنه این بازخورد اطلاعاتی مربوط به دیتابیس و چیزهای دیگه ای باشه که فقط برای شمای برنامه نویس فراهم شده نه برای کاربران،‌ شما در واقع باگ فیکس نکرده اید، بلکه باگ رو پنهان کرده اید یا در حالت دوم،‌ باگ بدتری ایجاد کرده اید.یک مزیت دیگه این هست که هر تغییری در کد خود ایجاد کردید، دیگه لازم نیست برنامه رو استارت کنید و بصورت دستی تست کنید تا ببینید این تغییرات جدید، کد قبلی رو break کرده یا نه. فقط کافیه تست  هایی که برای کد قبلی نوشتین رو اجرا کنین و هر وقت fail شد میفهمید که مشکل از کجاس و اینطوری سرعت توسعه و استحکام اپلیکیشن بالا میرهحالا که به اهمیت تست نرم افزار آگاه شدیم بریم با فریمورک هایی که برای اینکار در جاوا ساخته شده آشنا بشیم:1 - Junit2 - TestNG3 - Selenide4 - Spock...در مقاله در مورد Junit و نسخه ۵ ش حرف خواهیم زد. نکته جالب در مورد Selenide این هست که میتونید به عنوان crawler هم ازش استفاده کنید و بقولی در وب سایت ها بخزید!ایجاد پروژهبرای ایجاد پروژه میتونید از ide و کد ادیتور محبوب خودتون و build tool دلخواه استفاده کنیداضافه کردن کتابخونه junit 5 :gradle: testImplementation &#039;org.junit.jupiter:junit-jupiter:5.8.2&#039;maven:&lt;dependency&gt;
    &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
    &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;
     &lt;version&gt;5.8.2&lt;/version&gt;
     &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;تقریبا این فریمورک در هر پروژه جاوا توسط  IDE ها یا project generator ها  بصورت پیشفرض در وابستگی ها یا دپندنسی های پروژه اضافه میشود. فرض کنید همچین کلاسی در برنامه داریم:public class App {
    public String getGreeting() {
        return &amp;quotHello World!&amp;quot
    }
  public int division(int a, int b) {
    return a / b;
  }
}الان میخواهیم این دو تا متد این کلاس رو تست کنیم. برای اینکار لازمه یک کلاس تست در مسیر src/main/test/java/package_name ایجاد کنیم. معمولا اسم کلاس تست رو با کلاس که میخواهیم متد هاش رو تست کنیم همنام + Test قرار میدیم که در این مثال خاص به این صورت خواهد بود AppTestبرای تست واحد متد های کلاسمون میتونیم یک متد void با همان نام متد در کلاس اصلی بسازیم و با انوتیشن @Test اون رو به junit بفهمانیم که یک متد تست هست:import static org.junit.jupiter.api.Assertions.*;
class AppTest {
    @Test
    void getGreeting() {
        var classUnderTest = new App();
        assert classUnderTest.getGreeting() != null;
        assertEquals(&amp;quotHello, World!&amp;quot, classUnderTest.getGreeting());
    }
}در این مثال ما اول اومدیم یک آبجکت از کلاس اصلی مون ساختیم که به متد هاش دسترسی داشته باشیم. در خط بعدی از کلیدواژه assert جاوا استفاده کردیم که یک مقدار boolean رو بررسی میکنه و اگر false بود، تست رو در اصطلاح fail میکنه و در لاگ ارور AssertionError رو مشاهده خواهیم کرد. این کلیدواژه در اصل بیسیک ترین روش تست هست.در خط بعدی متد استاتیک assertEquals از کلاس Asserstions که استاتیک امپورت کرده ایم که دو آرگومان میگیرد اولی مقداری هست که ما انتظار داریم متد برگرداند،‌ دومی هم مقداری است که متد برمیگرداند. در ادامه از متدهای استاتیک این کلاس خیلی استفاده خواهیم کرد. این کلاس پر از متد هایی برای تست کردن عملکرد اجزای برنامه هست که میتونید خود کلاس رو باز کنید و با متدهاش آشنا بشین و جاواداک هایی خوبی هم نوشتن که بهتون میگه نحوه استفادشون چجوریه.در این تست ساده اول چک کردیم که متد ما null برنگرداند، دوم مقدار آن را بررسی کردیم.بریم برای تست متد دوم:@Test
void division() {
    var classUnderTest = new App();
    assertEquals(2, classUnderTest.division(4, 2));
}  

@Test
void divisionByZero() {
    var classUnderTest = new App();
    classUnderTest.division(4, 0);
}از آنجایی که متد ما میتونه دو تا حالت داشته باشه، یا مقداری برگردونه یا یک استثنا پرتاب کنه، پس ما دو متد تست براش مینویسیم که همه این حالت هارو پوشش بده.تست اول که تا اینجای کار باید بدونید چیکار میکنه اما بازم توضیح میدم: اول یک آبجکت از کلاس تحت تست رو ایجاد میکنیم و با متد استاتیک assertEquals، چیزی که از مقدار بازگشتی این متد انتظار داریم را در آرگومان اول و خود فراخونی متد رو در آرگومتن دوم با ورودی هایی که انتظار داریم در آخر برابر با مقدار آرگومان اول باشد رو وارد میکنیم.در تست دوم ما از عمد مقدار دوم آرگومان متد رو صفر گذاشتیم که برنامه به مشکل بخورد و ببینیم که توی کد اصلی راهکاری برای این مشکل اندیشیده ایم یا نه. اگر تست رو اجرا کنیم،‌تست بلافاصله با استثنای ArithmeticException شکست میخورد یا fail میشود. اینجاس که ما به یه مشکل در کد اصلی پی بردیم و میخواهیم که در این مثال بخصوص یک استثنای مناسب قبل از انجام عمل تقسیم به کاربر نشون بدهیم. کد اصلی رو به این صورت در میاوریم:public int division(int a, int b) {
    if (b == 0)
        throw new IllegalArgumentException(&amp;quotDivision by zero&amp;quot);
    return a / b;
}و تست را به اینصورت بازنویسی میکنیم:@Test
void divisionByZero() {
    var classUnderTest = new App();
    assertThrows(IllegalArgumentException.class, () -&gt; classUnderTest.division(4, 0));
}ایمجا اومدیم از متد  استاتیک assertThrows  استفاده کردیم که در آرگومان اولش نوع اکسپشنی که انتظار داریم این متد ما پرتاب کنه رو نوشتیم و در آرگومان دوم یک Executable که عمکردش عینا شبیه Runnable هست با این تفاوت که در این اینترفیس فانکشنال، متد execute در این اینترفیس throwable پرتاب میکند. این اینترفیس رو رو بصورت مدل لامبدا وارد میکنیم و داخل این لامبدا متد مورد نظر رو فراخونی کردیم اما اینبار با مقدار ۴ و ۰ چون تقسیم عدد بر صفر بینهایت هست و ما در متد اصلی این استثنا رو در صورتی پرتاب میکنیم که مقدار دوم صفر باشد. در این حالت ما برای متد اصلی مان چاره ای اندیشیده ایم و تست ما pass میشوددر خصوص junit چندتا انوتیشن های پرکابرد داریم که یکیش همین  Test هست:@BeforeEach
@AfterEach
@BeforeAll
@AfterAllشاید از اسم هاشون متوجه بشید که چه کاری میکنند. تمام این انوتیشن ها برای متد های void زده میشن که AfterAll و BeforeAll برای متد های static void هستن. کاربرد دوتای اول برای اجرای کد قبل و بعد هر تست هست و دوتای بعدی فقط یکبار اونم قبل و بعد از همه تست ها اجرا میشنبرای آشنایی بیشتر با انوتیشن ها و قابلیت های دیگه junit میتوانید به یک عالمه ویدیو و مقاله های آموزشی در اینترنت دسترسی داشته باشید که هزینه اش فقط یک سرچ ساده هستلینک این پروژه در گیتهاب : LINKاگر نظری، پیشنهادی،‌ اصلاحی دارید، لطفا در کامنت ها من و بقیه رو مطلع کنید ?</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Thu, 17 Mar 2022 10:26:37 +0330</pubDate>
            </item>
                    <item>
                <title>کلاس مجازی ادوبی در لینوکس 2021</title>
                <link>https://virgool.io/wptips/%DA%A9%D9%84%D8%A7%D8%B3-%D9%85%D8%AC%D8%A7%D8%B2%DB%8C-%D8%A7%D8%AF%D9%88%D8%A8%DB%8C-%D8%AF%D8%B1-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-2021-n2qz1h19ouem</link>
                <description>همونطور که میدونید شرکت ادوبی از سال ۲۰۲۱ پشتیبانی از فلش پلیر رو متوقف کرده و مرورگرا هم یکی یکی پلاگین های فلش رو حذف کردن. این وسط لینوکسی ها قربانی شدن. راه حلش یه کار ابداعی خودم بود که اینجوری میرم کلاسا۱- این extension رو برای کروم نصب کنید. کار این افزونه اینه که نذاره کلاسا بصورت پاپ آپ اجرا بشن که دلیلش رو میگم۲- وقتی لینک باز شد سریع کپی کنین اگه نکنین، ادوبی به یه لینک دیگه ریدایرکت میکنه. دلیل استفاده از اون افزونه اینه که سریعتر اینکارو انجام بدیملینک مورد انتظار:لینکی که سریع به این تغییر میکنه :پس سریع لینک مورد انتظار رو کپی کنین۳- وقتی لینک رو کپی کردین آخرش اینو اضافه کنین:&amp;proto=trueو اینتر رو بزنیناینجوری دیگه ادوبی چک نمیکنه که فلش نصبه یا نه. بعد یه صفحه ای میاد که باید open in browser رو بزنین* ممکنه اگه روی کروم اجرا کنین، صدای استاد درست نیاد. که میتونین روی فایرفاکس اجرا کنینحرف آخر: نمیدونم برنامه نویسای ادوبی کانکت یا اونقدر گشادن که حال ندارن این مورد رو فیکس کنن یا اینکه از عمد اینکارو میکنن. اصلا یه جا میگه html رو جایگزین فلش کردیم بعد تو اینجا فلش میخواد. بخدا سخت نیست یذره کد میخوان پاک کنن. امیدوارم مشکلتون رو حل کرده باشه</description>
                <category>fdbvcf</category>
                <author>fdbvcf</author>
                <pubDate>Wed, 07 Apr 2021 11:43:37 +0430</pubDate>
            </item>
            </channel>
</rss>