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

}

標準フレームワークを使用してテストする方法が見つかりません。特に、テスト内で失われたインターネットの合計をシミュレートする必要があります。

テストの実行中に、エミュレーターでインターネットを手動でオフにすることをお勧めします。しかし、自動テストは自動でなければならないので、それはあまり良い解決策ではないように思えます。

クラスに「クライアント」フィールドを追加して、テストクラス内からモックしようとしました。しかし、HttpClient インターフェースの実装は非常に複雑に見えます。

私の知る限り、 Robolectricフレームワークを使用すると、開発者はHTTP 接続をテストできます。しかし、それほど大きな追加フレームワークを使用せずに、そのようなテストを作成する方法がいくつかあると思います。

では、HttpClient を使用するクラスを単体テストするための短くて簡単な方法はありますか? プロジェクトでこれをどのように解決しましたか?

4

1 に答える 1

7

テストクラス内からモックを作成しようとして、クラスに「クライアント」フィールドを追加しました。ただし、HttpClientインターフェイスの実装は非常に複雑なようです。

私はこの声明について少し混乱しています。質問のタイトルから、httpClintの単体テストについて質問しています。FakeHttpClientをモックすることで、httpClientを除くアプリの他の部分の単体テストに役立つ場合がありますが、httpClientの単体テストには何の役にも立ちません。必要なのは、httpClientをユニットテストするためのFakeHttpLayerです(リモートサーバーは不要で、ネットワークは必要ないため、ユニットテストを行います)。

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を有効/無効にする1行のコードをサポートします。ここで、httpリクエストをFakeHttpLayerによって解釈されるように設定するか(ネットワーク経由で実際のhttp呼び出しを行わない)、またはhttpリクエストをFakeHttpLayerをバイパスするように設定できます(ネットワーク経由で実際のhttp呼び出しを実行します)。詳細については、このSOの質問を確認してください。

Robolectricのソースを確認すると、FakeHtppLayerを自分で適切に実装するのは非常に複雑であることがわかります。独自のAPIを実装する代わりに、既存のテストフレームワークを使用することをお勧めします。

お役に立てれば。

于 2012-04-13T12:28:18.393 に答える