如何使用内置框架对在Android中使用HttpClient的类进行单元测试?

时间:2012-04-13 07:12:44

标签: android unit-testing httpclient

我上课了:

public class WebReader implements IWebReader {

    HttpClient client;

    public WebReader() {
        client = new DefaultHttpClient();
    }

    public WebReader(HttpClient httpClient) {
        client = httpClient;
    }

    /**
     * Reads the web resource at the specified path with the params given.
     * @param path Path of the resource to be read.
     * @param params Parameters needed to be transferred to the server using POST method.
     * @param compression If it's needed to use compression. Default is <b>true</b>.
     * @return <p>Returns the string got from the server. If there was an error downloading file, 
     * an empty string is returned, the information about the error is written to the log file.</p>
     */
    public String readWebResource(String path, ArrayList<BasicNameValuePair> params, Boolean compression) {
            HttpPost httpPost = new HttpPost(path);
            String result = "";

            if (compression)
                httpPost.addHeader("Accept-Encoding", "gzip");
            if (params.size() > 0){
                try {
                    httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
                } catch (UnsupportedEncodingException e1) {
                    e1.printStackTrace();
                }
            }

            try {
                HttpResponse response = client.execute(httpPost);
                StatusLine statusLine = response.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                if (statusCode == 200) {
                    HttpEntity entity = response.getEntity();
                    InputStream content = entity.getContent();
                    if (entity.getContentEncoding() != null
                            && "gzip".equalsIgnoreCase(entity.getContentEncoding()
                                    .getValue()))
                        result = uncompressInputStream(content);
                    else
                        result = convertStreamToString(content);
                } else {
                    Log.e(MyApp.class.toString(), "Failed to download file");
                }
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            return result;
        }

    private String uncompressInputStream(InputStream inputStream)
            throws IOException {...}

    private String convertStreamToString(InputStream is) {...}

}

我找不到使用标准框架测试它的方法。特别是,我需要模拟测试内部丢失的互联网总量。

有建议在执行测试时手动关闭模拟器中的Internet。但在我看来,这并不是一个很好的解决方案,因为自动测试应该是......自动的。

我在类中添加了一个“client”字段,试图从测试类中模拟它。但是HttpClient接口的实现似乎相当复杂。

据我所知,Robolectric框架允许开发人员测试Http connection。但我想有一些方法可以在不使用如此大的额外框架的情况下编写这样的测试。

那么使用HttpClient的单元测试类有哪些简单直接的方法?你是如何在你的项目中解决这个问题的?

1 个答案:

答案 0 :(得分:6)

  

我在类中添加了一个“client”字段,试图从测试类中模拟它。但是HttpClient接口的实现似乎相当复杂。

我对这句话有点混淆。从问题标题,你问的是单元测试httpClint,通过模拟FakeHttpClient可以帮助你对除httpClient之外的应用程序的其他部分进行单元测试,但对单元测试httpClient没有任何帮助。你需要的是一个用于单元测试httpClient的FakeHttpLayer(没有远程服务器,网络要求,因此单元测试)。

HttpClient Dummy Test:

如果您只需要在互联网丢失的情况下检查应用行为,那么经典的Android乐器测试就足够了,您可以在执行测试时以编程方式关闭模拟器中的互联网:

public void testWhenInternetOK() {
  ... ...
  webReader.readWebResource();
  // expect HTTP 200 response.
  ... ...
}

public void testWhenInternetLost() {
  ... ...
  wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE); 
  wifiManager.setWifiEnabled(false);
  webReader.readWebResource();
  // expect no HTTP response.
... ...
}

这要求远程http服务器完全设置并处于工作状态,并且每当您运行测试类时,都会通过网络进行真正的http通信并在http服务器上进行点击。

HttpClient高级测试:

如果您想更精确地测试应用行为,例如,您希望在应用中测试一个http调用,以查看它是否正确处理了不同的http响应。 Robolectric是最好的选择。您可以使用FakeHttpLayer并模拟http请求和响应您喜欢的任何内容。

public void setup() {
  String url = "http://...";
  // First http request fired in test, mock a HTTP 200 response (ContentType: application/json)
  HttpResponse response1 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 200, null);
  BasicHttpEntity entity1 = new BasicHttpEntity();
  entity1.setContentType("application/json");
  response1.setEntity(entity1);
  // Second http request fired in test, mock a HTTP 404 response (ContentType: text/html)
  HttpResponse response2 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 404, null);
  BasicHttpEntity entity2 = new BasicHttpEntity();
  entity2.setContentType("text/html");
   response2.setEntity(entity2);
  List<HttpResponse> responses = new ArrayList<HttpResponse>();
  responses.add(response1);
  responses.add(response2);
  Robolectric.addHttpResponseRule(new FakeHttpLayer.UriRequestMatcher("POST", url), responses);
}

public void testFoo() {
  ... ...
  webReader.readWebResource(); // <- a call that perform a http post request to url.
  // expect HTTP 200 response.
  ... ...
}

public void testBar() {
  ... ...
  webReader.readWebResource(); // <- a call that perform a http post request to url.
  // expect HTTP 404 response.
... ...
}

使用Robolectric的一些优点是:

  • 纯粹是JUnit测试,没有仪器测试所以不需要启动仿真器(或真实设备)来运行测试,提高开发速度。
  • 最新的Robolectric支持单行代码启用/禁用FakeHttpLayer,您可以在其中设置由FakeHttpLayer解释的http请求(没有通过网络进行真正的http调用),或者设置http请求绕过FakeHttpLayer(执行真正的http调用网络)。查看this SO question了解详情。

如果你查看了Robolectric的来源,你可以看到自己正确实现FakeHtppLayer非常复杂。我建议使用现有的测试框架,而不是实现自己的API。

希望这有帮助。