Как написать модульный тест метода static void

Я столкнулся с проблемой, я не знаю, как написать модульный тест метода static void.

У меня есть класс HttpHelper, который прямо сейчас использует Apache HttpClient. Код, как показано ниже.

public class HttpHelper {
    private static CloseableHttpClient httpClient;

    public static void init() {
        httpClient = HttpClients.custom().setSSLContext(getDummySSL()).build();
    }

    public static void closeHttpClient() throws IOException {
        httpClient.close();
    }

    private static SSLContext getDummySSL() {
        ...omit
    }

    private static void send() {
        HttpGet httpGet = new HttpGet("https://someUrl.com");

        try(CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
            if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                responseString = EntityUtils.toString(httpResponse.getEntity());
                // do something
            } else {
                throw new Exception();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Итак, в моем main я вызову HttpHelper.init() для инициализации httpClient. Каждый раз, когда я хочу отправить запрос, я звоню HttpHelper.send(). Потому что я не хочу каждый раз создавать новый httpClient. В конце я вызову HttpHelper.close(), чтобы закрыть httpClient.

Мне интересно, как проверить эти пустые методы. Моя концепция состоит в том, чтобы создать один CloseableHttpClient в моем тесте, а затем вызвать HttpHelper.init(), чтобы создать фактический. затем сравните мой ожидаемый и фактический один и тот же. Я прав?

Из-за того, что переменная и методы объявлены как статические. Немного сложно писать модульные тесты. Есть много сообщений, в которых говорится, что делать методы статическими - плохая практика. Однако в моем примере я не знаю, как избежать объявления их статическими и сохранить один экземпляр CloseableHttpClient.

Благодарю вас!


person rascee    schedule 27.10.2020    source источник
comment
Боль при модульном тестировании обычно указывает на запах кода. Возможно, передайте httpClient в send() (чтобы httpClient можно было легко издеваться), или измените на нестатический и введите httpClient (чтобы httpClient можно было легко издеваться).   -  person Andrew S    schedule 27.10.2020
comment
Здравствуйте, спасибо за ответ. Но если я так делаю. может быть, httpClient не может хранить один экземпляр?   -  person rascee    schedule 27.10.2020


Ответы (2)


Наличие такого статического класса — это плохо, потому что его очень сложно протестировать. Я понимаю, почему вам это нужно, но у вас могут быть все те же преимущества:

public class HttpHelper {

    private static HttpHelper DEFAULT_INSTANCE = null;

    private CloseableHttpClient httpClient;

    public HttpHelper(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public static void getDeafultInstance() { // this should probably be synchronised for thread safety
        if (DEFAULT_INSTANCE == null) {
            DEFAULT_INSTANCE = httpClient = HttpClients.custom().setSSLContext(getDummySSL()).build();
        }
        return DEAFULT_INSTANCE;
    }

    private static SSLContext getDummySSL() {
        ...omit
    }

    public void closeHttpClient() throws IOException {
        httpClient.close();
    }

    private void send() {
        HttpGet httpGet = new HttpGet("https://someUrl.com");

        try(CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
            if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                responseString = EntityUtils.toString(httpResponse.getEntity());
                // do something
            } else {
                throw new Exception();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Затем вы можете выполнить модульное тестирование следующим образом:


public class HttpHelperTest {

    @Test
    public testSendsRequestToSomeUrl() {
        CloseableHttpClient httpClientMock = mock();
        when(httpClient.execute(any())).thenReturn(..http_response_where_stauts_code_is_ok..)
        HttpHelper httpHelper = new HttpHelper(httpClientMock)
        httpHelper.send()
        verify(httpClient).execute(new HttpGet("https://someUrl.com"))
    }

}

и используйте его в реальном коде следующим образом:

HttpHelper.getDeafultInstance().send()

P.S.

Если у вас есть какая-то доступная среда внедрения зависимостей, вы можете вообще избавиться от статических методов.

person Tarmo    schedule 27.10.2020

Гарантия одного экземпляра в основном решается с помощью шаблона Singleton. Обычный трюк для модульного тестирования — создать конструктор с защищенной видимостью, в который вы можете поместить аргументы для тестирования. Наконец-то класс мог выглядеть так.

public class HttpHelper {
    private static HttpHelper INSTANCE = new HttpHelper();

    public static HttpHelper getInstance() {
        return INSTANCE;
    }


    private CloseableHttpClient httpClient;

    private HttpHelper() {
        SSLContext sslContext = getDummySSL();
        this(HttpClients.custom().setSSLContext(sslContext).build(), sslContext);
    }

    protected HttpHelper(CloseableHttpClient httpClient, SSLContext sslContext) {
        this.httpClient = httpClient;
    }

    public void closeHttpClient() throws IOException {
        httpClient.close();
    }

    private static SSLContext getDummySSL() {
        ...
    }

    private void send() {
        ...
    }
}

Я бы также переименовал getDummySSL в createDummySSL, но это детали.

person Marc H.    schedule 27.10.2020