Как смоделировать objectOutputStream.writeObject ()? - PullRequest
0 голосов
/ 24 марта 2019

Что я пытаюсь сделать

У меня есть серверный класс, у которого нет преднамеренных аргументов, и я хочу протестировать его с помощью Mockito.

Если вы хотите проверить полный исходный код на Github :

Server.class

public class Server extends Thread {
    private Other other;
    ObjectInputStream fromClient;
    ObjectOutputStream toClient;


    public Server(){
        this.other = new Other(foo, bar);
    }


    @Override
    public void run(){
        try{

            ServerSocket serverSocket = new ServerSocket(1337);
            Socket socket = serverSocket.accept();

            fromClient = new ObjectInputStream(socket.getInputStream());
            toClient = new ObjectOutputStream(socket.getOutputStream());

            while(true) {
                int command = (Integer) fromClient.readObject();
                switch (command) {
                    case 0x1:
                        //add
                        //...
                        break;
                    case 0x2:
                        //get server data
                        toClient.writeObject(other.getSomething());
                        break;
                    case 0x3:
                        //delete
                        //...
                        break;
                    default:
                        break;
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Thread t = new Server();
        t.start();
    }
}

Проблема

Я понимаю, что Mockito не может смоделировать конечные классы, такие как ObjectOutputStream и ObjectInputStream.

Это как раз то место, где я сталкиваюсь с проблемами.

Пока мой тест не проходит с NullPointerException в строке

when(server.fromClient.readObject()).thenReturn(0x2);.

Этотипично для Mockito, при запуске в финальных методах.

ServerTest.class

@Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.fromClient.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());

    }

Что я пробовал

Было предложено в другие сообщения о том, что можно обойти final -проблему, изменив сигнатуру тестируемого класса, реализовав интерфейс ObjectInput и, следовательно, посмеявшись над ним.

Однаков предлагаемом подходе неясно, как работать, если не передать ObjectOutputStream в качестве аргумента тестируемому классу.

Кроме того, как работать, когда у вас есть ObjectInputStream, непосредственно управляющий ответом ObjectOutputStream как видно из моей case структуры, которая не является нетипичной для клиентских / серверных приложений TCP.

Пока у меня сложилось впечатление, что мой тест будет работать, не будет ли он для последнего ключевого словав подписи ObjectOutputStream,Поправьте меня здесь, если я ошибаюсь.

Q: "Почему я не передаю потоки на сервер?" A: Потому что они мне больше не нужны.

Это действительно последняя попытка сделать это, и я сделаю это, если мне придется, но я бы не стал.

Ответы [ 2 ]

2 голосов
/ 24 марта 2019

Вы можете предоставить функцию в вашем классе Server, которая читает fromClient входной поток и смоделирует ее вместо ObjectInputStream.readObject() метода:

public class Server extends Thread {
     private Other other;
     ObjectInputStream fromClient;
     ObjectOutputStream toClient;


     public Server(){
         this.other = new Other(foo, bar);
     }

     Object readObject() throws ClassNotFoundException, IOException {
         return fromClient.readObject();
     }

     //...

Итак, ваш тест будет выглядеть так:

    @Test
    void run() throws IOException, ClassNotFoundException {
        //given
        Other other = new Other(foo, bar);
        Server server = mock(Server.class);

        //when
        when(server.readObject()).thenReturn(0x2);

        server.start();

        //then
        verify(server).toClient.writeObject(other.getSomething());
    }

То же самое можно применить и к toClient потоку.

1 голос
/ 25 марта 2019

Недопонимание в вашем вопросе

Я понимаю, что Mockito не может высмеивать конечные классы, такие как ObjectOutputStream и ObjectInputStream.> Это как раз то место, где я сталкиваюсь с проблемами.

Пока что мой тест не пройден с NullPointerException в строке

when (server.fromClient.readObject ()). ThenReturn (0x2) ;.

Это типично для Mockito, когда он работает с финальными методами.

Ваш NPE не , вызванный неспособностью Mockitos высмеивать final методы.Причина NPE в том, что ваши переменные-члены fromClient и toClient не инициализируются, когда ваш тестовый код пытается получить к ним доступ.

Но даже если они будут инициализированы с экземплярами реальных зависимостей ине с mocks , чтобы Mockito не смог их настроить в любом случае.

Вы в корне неправильно понимаете цель и технические последствия mocks.Вы никогда не высмеиваете тестируемый класс.Вы всегда издеваетесь над зависимостями вашего тестируемого класса и заменяете настоящими зависимостями вашего тестируемого кода этими насмешками.

В других постах предлагалось обойтисьfinal -проблема путем изменения сигнатуры тестируемого класса путем реализации интерфейса ObjectInput и, следовательно, насмешки над ним.

Нет.

Вы предложили программировать по интерфейсам вместо программирования по конкретным классам и использовать Внедрение зависимостей / инверсия управления .

Никто не предлагал, чтобы тестируемый вами класс (который является вашим Server классом) должен реализовывать какой-либо интерфейс.

Нарушение рекомендаций по ООП

Q: "Почему я не передаю потоки на сервер? "
A: Потому что они мне больше нигде не нужны.

Это действительно последняя попытка сделать это, и я буду, если придется,но я бы предпочел не делать этого.

Одна из основных причин, по которой мы делаем ООП, - возможность повторного использования нашего кода.

Наиболее часто модульный тесточевидное первое повторное использование вашего кода.Проблемы с тестированием кода выявляют отсутствие возможности повторного использования.На этом этапе не имеет значения, что ваш класс Server вообще не предназначен для повторного использования.Это ваш общий подход к кодированию, который позволит вам столкнуться с проблемами в будущем.

В вашем Server классе есть некоторые нарушения лучших практик ООП:

  • инкапсуляция / информацияhidig.

    Вы объявили переменные-члены fromClient и toClient package private , скорее всего, чтобы иметь возможность получить к ним доступ из нашего теста.Это проблематично двумя способами:

    1. Ваш код никогда не должен предоставлять подробности реализации как переменные-члены (за исключением того, что это DTO / ValueObject).
    2. Никогда не следует явно изменять производственный код для включения тестирования.
  • разделение задач

    Любой класс (обеспечивающий бизнес-логику) долженнесут одиночную ответственность .Установление зависимостей - это совершенно иная ответственность, чем та, что есть в вашей бизнес-логике.Так что ваш класс Server не должен отвечать за выполнение этого экземпляра.

    Я знаю, что это немного догматично, но это приводит к лучшему тестированию и, следовательно, к лучшему к повторному использованию коду.И снова: не имеет значения, если ваш код в настоящее время не предназначен для повторного использования вообще.

Заключение

ИМХО ваш реальныйВопрос в следующем: Как я могу протестировать свой код, не адаптируя лучшие практики ООП?

В этом случае вы должны явно зафиксировать свое отказ от плохого дизайна, используя PowerMock .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...