如何编写静态 void 方法的单元测试。

huangapple go评论121阅读模式
英文:

How to write unit test of a static void method

问题

我正在面临一个问题,我不知道如何编写静态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() {
        ...省略
    }

    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());
                // 做一些操作
            } else {
                throw new Exception();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

因此,在我的主程序中,我会调用HttpHelper.init()来初始化httpClient。每次我想发送请求时,我会调用HttpHelper.send()。因为我不想每次都创建一个新的httpClient。最后,我会调用HttpHelper.close()来关闭httpClient。

我想知道如何测试这些void方法。我的想法是在测试中创建一个CloseableHttpClient,然后调用HttpHelper.init()来创建实际的httpClient。然后比较我的预期对象和实际对象是否相同。我是对的吗?

由于变量和方法被声明为静态,编写单元测试有点困难。有很多帖子说将方法设为静态是一种不好的做法。然而,在我的示例中,我不知道如何避免将它们声明为静态并保持单个CloseableHttpClient实例。

谢谢!

英文:

I am facing a problem, I don't know how to write unit test of static void method.

I have a HttpHelper class that using Apache HttpClient right now. Code like below.

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();
        }
    }
}

So in my main I will call HttpHelper.init() to initialize the httpClient. Everytime I wanna send a request, i will call HttpHelper.send(). Because I don't want to create a new httpClient everytime. At the end, I will call HttpHelper.close() to close the httpClient.

I am wondering how to test those void methods. My concept is to create one CloseableHttpClient in my test, then call HttpHelper.init() to create the actual one. then compare my expected one and actual one is same. Am I right?

Due to the variable and methods are declare as static. It is a bit difficult to write unit tests. There are many posts said making methods static is a bad practice. However in my example, I don't know how to avoid declaring them as static and keep a single CloseableHttpClient instance.

Thank you!

答案1

得分: 1

单例模式基本上解决了单个实例的保证问题。一个常见的单元测试技巧是创建一个受保护可见性的构造函数,在这里你可以放置用于测试的参数。该类最终可能如下所示:

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

    public static HttpHelper getInstance() {
        return INSTANCE;
    }

    private CloseableHttpClient httpClient;

    private HttpHelper() {
        SSLContext sslContext = createDummySSL();
        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 createDummySSL() {
        ...
    }

    private void send() {
        ...
    }
}

我还会将 getDummySSL 重命名为 createDummySSL,但这只是一个细节。

英文:

Single instance guarantee is mostly solved with the Singleton pattern. A common trick for unit testing is to make a constructor with protected visbility where you can put the arguments for testing. The class could finally look like this.

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() {
        ...
    }
}

I would also rename the getDummySSL to createDummySSL but this is detail.

答案2

得分: 0

拥有如此静态的类是不好的因为很难对其进行测试我理解你为什么想要这样但你可以通过以下方式获得相同的好处

public class HttpHelper {

    private static HttpHelper DEFAULT_INSTANCE = null;

    private CloseableHttpClient httpClient;

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

    public static HttpHelper getDefaultInstance() { // 这可能需要同步以确保线程安全
        if (DEFAULT_INSTANCE == null) {
            DEFAULT_INSTANCE = new HttpHelper(HttpClients.custom().setSSLContext(getDummySSL()).build());
        }
        return DEFAULT_INSTANCE;
    }

    private static SSLContext getDummySSL() {
        ...省略
    }

    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) {
                String responseString = EntityUtils.toString(httpResponse.getEntity());
                // 做一些操作
            } else {
                throw new Exception();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class HttpHelperTest {

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

}
HttpHelper.getDefaultInstance().send();

附注:

如果你有某种依赖注入框架可用,那么你可以完全摆脱静态方法。


<details>
<summary>英文:</summary>
Having a class so static is bad, because it&#39;s very hard to test this. I understand why you would want this, but you could have all the same benefits like that:

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(&quot;https://someUrl.com&quot;);
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();
}
}

}


Then you could unit test it like that:

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(&quot;https://someUrl.com&quot;))
}

}


and use it in actual code like that:

HttpHelper.getDeafultInstance().send()


P.S.
If you have some kind of dependency injection framework available, then you could get rid of the static methods at all.
</details>

huangapple
  • 本文由 发表于 2020年10月27日 23:59:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/64558275.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定