Как я могу использовать PowerMock для проверки статического метода, который вызывает другой статический метод? - PullRequest
0 голосов
/ 27 февраля 2019

PowerMock - отличный инструмент, который я недавно начал использовать для тестирования некоторых статических методов.К сожалению, я НЕ могу переписать что-либо (кроме тестов), и мне нужно, чтобы PowerMock мог тестировать этот код строго как есть.

Это мой тест PowerMock:

import java.io.*;
import org.junit.*;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;

import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.core.classloader.annotations.PrepareForTest; 

@RunWith(MockitoJUnitRunner.class)
@PrepareForTest({Solution.class})
public class SolutionTest {

    // stream to record the output (System.out)
    private ByteArrayOutputStream testOutput;

    @Before
    public void setUpOutputStream() {
        testOutput = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOutput));
    }

    // input feed to Scanner (System.in)
    private void setInput(String input) {
        System.setIn(new ByteArrayInputStream(input.getBytes()));
    }

    @Test
    public void test1() {
        // set System.in
        setInput("foo");

        final String expected = "foobar";
        final String actual = testOutput.toString();

        // run the program (empty arguments array)
        Solution.main(new String[0]);

        assertEquals(expected, actual);
    }

    @Test
    public void test2() {
        setInput("new");
        Solution.main(new String[0]);
        final String expected = "newbar";
        final String actual = testOutput.toString();
        assertEquals(expected, actual);
    }
}

PowerMock позволил мне выполнить (и пройти) два теста подряд на статическом методе в сценарии, подобном следующему:

import java.util.Scanner;
public class Solution {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        String input = scanner.nextLine();

        scanner.close();

        System.out.print(input + "bar");
    }
}

До PowerMock я был заблокирован исключением(вызвано необходимостью проверки статического метода) java.lang.IllegalStateException: Scanner closed

Однако в этом альтернативном сценарии, который вызывает второй статический метод (также сканер является статическим членом), эта проблема вновь возникает.

import java.util.Scanner;
public class Solution {

    static void printString(String s) {
        System.out.print(s);
    }

    private static final Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {

        String input = scanner.nextLine();

        printString(input + "bar");

        scanner.close();
    }
}

Здесь test1 будет пройден, но test2 даже не сможет работать из-за java.lang.IllegalStateException: Scanner closed

Мне нужно пройти оба теста в последнем сценарии, как и в первом.

Для вашего удобства (и поскольку проверенный ответ будет наиболее ценным), мои зависимости следующие:

<dependencies>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Большое спасибо!

Ответы [ 2 ]

0 голосов
/ 16 июля 2019

Я столкнулся с проблемой, подобной этой.Я пытался создать локальную среду тестирования для задач HackerRank.Моя цель состоит в том, чтобы иметь возможность завершить свое решение в предоставленном классе с неполной готовностью Solution и проверить его на соответствие тестовым примерам, загруженным с их веб-сайта, без необходимости изменения шаблонных кодов для каждой задачи.

В другихслова, у меня есть класс Solution с кодами, которые я не могу (читай: не хочу) касаться, который включает в себя ввод, прочитанный scanner из System.in:

    private static final Scanner scanner = new Scanner(System.in);

Я пытался убедиться, что System.in установлено на желаемое значение до того, как будет создан экземпляр сканера final static, но, как мы видим по определению, этот сканер отнюдь не легко модифицировать, чтобы настроить его для другого тестаслучаи.

И еще один хитрый бит, в классе Solution, вывод настроен на запись в файл, местоположение которого получено из переменной среды, используя System.getenv("OUTPUT_PATH").Это создает проблему, потому что тесты могут выполняться параллельно и пытаться записать результаты в один и тот же файл, как указано в этой переменной среды.

Короче говоря, я закончил тем, что высмеял System.class, используяPowerMock, создайте вложенный класс для каждого из тестовых случаев и добавьте @PrepareForTest для каждого класса тестовых случаев, и в конечном итоге это сработало для меня.Ниже приведен мой код для класса DoAllTest, который содержит всю информацию, относящуюся к конкретному вызову, в данном случае это вызов "игра в бомбермена".Для этого испытания есть два теста 00 и 25.Удивительно, что объект SolutionWrap, который содержит экземпляр Solution в следующем коде, на самом деле является общим для обоих тестов, но PowerMock заботится о насмешке над System.class, и они работают так, как если бы они были в отдельности "контейнеры ".

package practice.thebombermangame;
import common.SolutionTest;
import common.SolutionTestable;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.junit.experimental.runners.Enclosed;

@RunWith(Enclosed.class)
public class DoAllTest {
    class SolutionWrap implements SolutionTestable {
        public void runMain(String[] args) {
            try {
                Solution s = new Solution();
                s.main(args);
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
        };
    };

    static SolutionWrap solutionWrap = new DoAllTest().new SolutionWrap();

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Solution.class, SolutionTest.class, Test1.class})
    public static class Test1 {
        @Test
        public void test1() {
            String testIDString = "00";
            String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt";
            String outputFileName = "out_path/output" + testIDString  + ".txt";
            String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt";
            SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName);
            solutionTest.doTest(solutionWrap);
        }
    };

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Solution.class, SolutionTest.class, Test2.class})
    public static class Test2 {
        @Test
        public void test2() {
            String testIDString = "25";
            String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt";
            String outputFileName = "out_path/output" + testIDString  + ".txt";
            String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt";
            SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName);
            solutionTest.doTest(solutionWrap);
        }
    };
}


Класс SolutionTest является общим для всех задач, System.in и переменная среды изменяется в нем, как показано ниже:

package common;
import java.io.FileInputStream;
import java.io.IOException;
import org.powermock.api.mockito.PowerMockito;
import org.mockito.Mockito;

public class SolutionTest {
    static String inputFileName;
    String outputFileName;
    String correctFileName;

    public SolutionTest(String inputFileName_, String outputFileName_, String correctFileName_) {
        inputFileName = inputFileName_;
        outputFileName = outputFileName_;
        correctFileName = correctFileName_;
        setSystemIn();
    }

    final static void setSystemIn() {
        try {
            System.out.println("Setting System.in to " + inputFileName);
            System.setIn(new FileInputStream(inputFileName));
        } catch(IOException e) {
            System.err.println(e.getMessage());
        }
    }

    public void doTest(SolutionTestable solutionTestable) {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getenv(Mockito.eq("OUTPUT_PATH"))).thenReturn(outputFileName);
        SampleTest sampleTest = new SampleTest();
        sampleTest.testMain(solutionTestable, outputFileName, correctFileName);
    }
};

Как вы можетевидите, setSystemIn() вызывается при создании объекта SolutionTest и устанавливает System.in в inputFileName, который передается в конструктор.При использовании System.class для объекта scanner можно установить желаемое значение.

0 голосов
/ 27 февраля 2019

Я попробовал что-то, сочетающее фиктивные конструкторы Функция PowerMock с фиктивными классами (в отличие от фиктивные интерфейсы ) возможность Мокито, но безуспешно: проблема, которую я пыталсянеобходимо решить, что Scanner создание экземпляра происходит до вызова setInput, поэтому я попытался с

private static Scanner scannerMock;

    static {
        try {
            scannerMock = Mockito.mock(Scanner.class);
            PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scannerMock);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setInput(String input) throws Exception {
        PowerMockito.when(scannerMock.nextLine()).thenReturn(input);
    }

Это может работать с другими классами, но не с классом Scanner, потому что это final.Я думаю, что нет решения вашей проблемы, когда вы не можете немного изменить Solution класс: в прошлом это работало для меня, как такой подход, представленный (отказ от ответственности) здесь , но этоочевидно, не работает без свободы изменения кода Solution.

Может быть, вы можете использовать отражение для доступа к private static final полю Scanner, установив его в Scannerэкземпляр, который вы создали ранее, и которым вы можете управлять, как описано в принятом ответе на этот вопрос : может быть, это не самый чистый способ написания теста, но я думаю, что он может сработать и решить вашу проблему.

Надеюсь, это поможет вам найти приемлемое и жизнеспособное решение ...

...