برنامه نویسی

نقض MVC بازی تیک تاک او را خراب کرد!

1. بررسی اجمالی

دوست من در حال کار بر روی یک بازی جدید VR جالب است و از من خواست تا با نکات و ترفندهایی در مورد نحوه آزمایش آن کمک کنم.
PS: با قسمت VR شوخی کردم، این یک بازی tic-tac-toe کنسولی خواهد بود:

public class Player {
    public static final int MAX_NUMBER = 100;
    public static final int MIN_NUMBER = 10;
    private final Scanner scanner = new Scanner(System.in);

    public int getNextMove() {
        System.out.println("please, type your next move and hit <enter>");
        int input = scanner.nextInt();

        while(input < MIN_NUMBER || input > MAX_NUMBER) {
            System.out.println(String.format("the number must between %d and %d! try again...", MIN_NUMBER, MAX_NUMBER));
            input = scanner.nextInt();
        } 
        // other game-related logic here
        return input;
    }
}
وارد حالت تمام صفحه شوید

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

حتی اگر امکان انجام برخی «هک‌ها» و آزمایش این روش وجود دارد، آزمایش‌های زشت ممکن است نشانه‌های طراحی بد باشند – بنابراین همیشه ایده خوبی است که ابتدا کد را اصلاح کنید.

2. نقض MVC

اولاً، هیچ جدایی بین مدل، نما و کنترلر وجود ندارد. این منجر به جفت شدن زیادی بین منطق بازی و ورودی ای می شود که از طریق ترمینال می آید.
بهترین راه برای حذف جفت مستقیم او استفاده از اصل وارونگی وابستگی است. در این مورد، اجازه دهید رابطی را اعلام کنیم که امکان ارتباط کاربر را فراهم می کند. ما آن را “PlayerView” می نامیم:

public interface PlayerView {
    int readNextInt();
    void write(String message);
}
وارد حالت تمام صفحه شوید

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

شی Player فقط در مورد این رابط می داند. در این مرحله، ما فقط به کلاس‌های خودمان وابسته هستیم (دیگر از System.out، System.in، Scanner و غیره استفاده نمی‌کنیم):

public class Player {
    public static final int MAX_NUMBER = 100;
    public static final int MIN_NUMBER = 10;
    private final PlayerView view;

    public Player(PlayerView console) {
        this.view = console;
    }

    public int getNextMove() {
        view.write("please, type your next move and hit <enter>");
        int input = view.readNextInt();

        while(input < MIN_NUMBER || input > MAX_NUMBER) {
            view.write(String.format("the number must between %d and %d! try again...", MIN_NUMBER, MAX_NUMBER));
            input = view.readNextInt();
        }
        return input;
    }
}
وارد حالت تمام صفحه شوید

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

3. وارونگی وابستگی

چگونه کاری کنیم که کار کند؟ خوب، برای اینکه آن را مانند قبل کنیم، اکنون باید یک پیاده سازی از رابط PlayerView ایجاد کنیم و عملکرد قدیمی را کپی کنیم:

public class ConsoleView implements PlayerView {
    private static final Scanner scanner = new Scanner(System.in);

    @Override
    public int readNextInt() {
        return scanner.nextInt();
    }

    @Override
    public void write(String message) {
        System.out.println(message);
    }
}
وارد حالت تمام صفحه شوید

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

در نتیجه، زمانی که بازی مقدار دهی اولیه می شود، باید بازیکنان را بر اساس ConsoleView ایجاد کنیم، مانند این:

Player player = new Player(new ConsoleView());
وارد حالت تمام صفحه شوید

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

4. نوشتن یک موک

چه دستاوردهایی دارد؟ پس از این وارونگی وابستگی، کلاس Player را می توان به راحتی آزمایش کرد. به عنوان مثال، ما می توانیم از یک کتابخانه تمسخر آمیز برای تمسخر نمای استفاده کنیم و به ما اجازه می دهیم بقیه عملکردها را به صورت مجزا آزمایش کنیم. در جاوا، mockito یک ابزار محبوب و قدرتمند برای این کار است.
از سوی دیگر، استفاده کورکورانه از کتابخانه ها و چارچوب ها ممکن است باعث شود ما تصویر بزرگتر را از دست بدهیم. بنابراین، هر از گاهی، بهتر است به جای آوردن یک کتابخانه شخص ثالث، راه حل را خودمان کدنویسی کنیم.
بنابراین، اجازه دهید یک نسخه بسیار ساده از یک شی ساختگی ایجاد کنیم. ما می توانیم این کار را به سادگی با نوشتن یک پیاده سازی جدید از رابط PlayerView انجام دهیم:

public class MockView implements PlayerView {
    private List<Integer> mockedUserInputs = new ArrayList<>();
    private List<String> displayedMessages = new ArrayList<>();

    @Override
    public int readNextInt() {
        return mockedUserInputs.remove(0);
    }

    @Override
    public void write(String message) {
        displayedMessages.add(message);
    }

    public List<String> getDisplayedMessages(){
        return displayedMessages;
    }

    public void mockedUserInputs(Integer... values) {
        mockedUserInputs.addAll(Arrays.asList(values));
    }
}
وارد حالت تمام صفحه شوید

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

همانطور که می بینیم، از دو لیست استفاده می کنیم
mockedUserInputs در تنظیمات تست مشخص خواهد شد. هر بار که کسی readNextInt() را فراخوانی می کند، mock مقدار بعدی را از لیست برمی گرداند.
displayedMessages فقط گیرنده دارد. این لیست برای ذخیره تمام پیام هایی که می خواهیم چاپ کنیم استفاده می شود. در صورتی که بخواهیم بررسی کنیم که پیام ها به درستی نمایش داده می شوند، می تواند مفید باشد.

5. تست واحد

در نهایت، بیایید از کلاس ساختگی خود استفاده کنیم و چند تست واحد بنویسیم:

@Test
void shouldAskUserToEnterNextMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(11);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    player.getNextMove();

    //then
    List<String> displayedMessages = mockedView.getDisplayedMessages();
    assertThat(displayedMessages)
        .containsExactly("please, type your next move and hit <enter>");
}

@Test
void givenInvalidInput_shouldAskUserToReEnterTheMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(5, 22);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    player.getNextMove();

    //then
    assertThat(mockedView.getDisplayedMessages())
        .containsExactly(
            "please, type your next move and hit <enter>",
            "the number must between 10 and 100! try again...");
}

@Test
void shouldReturnUsersMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(44);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    int userMove = player.getNextMove();

    //then
    assertThat(userMove)
        .isEqualTo(44);
}
وارد حالت تمام صفحه شوید

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

6. نتیجه گیری

در این مقاله با الگوی طراحی MVC آشنا شدیم. ما از اصل وارونگی وابستگی (“D” در “SOLID”) برای جدا کردن کنترلر از نما استفاده کردیم.
در نهایت، ما توانستیم آن را آزمایش کنیم و یاد گرفتیم که چگونه یک شی ساختگی بسیار ساده برای شبیه سازی تعامل کاربر ایجاد کنیم.

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

متشکرم!

از خواندن مقاله متشکرم و لطفاً نظر خود را به من بگویید! هر گونه بازخورد استقبال می شود.

اگر می‌خواهید در مورد کد تمیز، طراحی، تست واحد، برنامه‌نویسی عملکردی و بسیاری موارد دیگر اطلاعات بیشتری کسب کنید، حتماً مقالات دیگر من را بررسی کنید.

اگر محتوای من را دوست دارید، فهرست ایمیل را دنبال کنید یا در آن مشترک شوید. در نهایت، اگر در نظر دارید از وبلاگ من حمایت کنید و برای من قهوه بخرید، سپاسگزار خواهم بود.

کد نویسی مبارک!

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

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

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

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