5

我有一堂课:

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。但在我看来,这不是一个很好的解决方案,因为自动测试应该是......自动的。

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

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

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

4

1 回答 1

7

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

我对这个说法有点困惑。从问题标题中,您正在询问有关单元测试 httpClint 的问题,通过模拟 FakeHttpClient 可以帮助您对除 httpClient 之外的应用程序的其他部分进行单元测试,但对单元测试 httpClient 没有任何帮助。您需要的是一个 FakeHttpLayer 用于对 httpClient 进行单元测试(无需远程服务器,需要网络,因此需要进行单元测试)。

HttpClient 虚拟测试:

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

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,您可以在其中设置 http 请求由 FakeHttpLayer 解释(不通过网络进行真正的 http 调用),或设置 http 请求绕过 FakeHttpLayer(通过网络执行真正的 http 调用)。查看这个 SO question了解更多详情。

如果您查看 Robolectric 的源代码,您会发现自己正确实现 FakeHtppLayer 非常复杂。我建议使用现有的测试框架而不是实现自己的 API。

希望这可以帮助。

于 2012-04-13T12:28:18.393 回答