我有以下类,其中包含永远不会更改的硬编码URL:
public class HttpClient {
private final String DOWNLOAD_URL = "http://original.url.json";
public String readJsonDataFromUrl() throws IOException {
URLConnection urlConnection = getUrlConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuffer content = new StringBuffer();
String readLine = "";
while ((readLine = reader.readLine()) != null) {
content.append(readLine);
}
return content.toString();
}
private URLConnection getUrlConnection() throws IOException {
URL jsonLocator = new URL(DOWNLOAD_URL);
return jsonLocator.openConnection();
}
}
现在想象一下,我想在我的测试中期待IOException。在我看来,唯一的方法是在模拟对象中重写完整的类,因为最终的变量:
public class HttpClientMock extends HttpClient {
private final String DOWNLOAD_URL = "http://wrong.test.url.json";
@Override
public String readJsonDataFromUrl() throws IOException {
URLConnection urlConnection = getUrlConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuffer content = new StringBuffer();
String readLine = "";
while ((readLine = reader.readLine()) != null) {
content.append(readLine);
}
return content.toString();
}
private URLConnection getUrlConnection() throws IOException {
URL jsonLocator = new URL(DOWNLOAD_URL);
URLConnection urlConnection = jsonLocator.openConnection();
return urlConnection;
}
}
但这在某种程度上是牵强附会的。如果原始方法会被更改,测试结果仍然可能是正面的,因为通过这种尝试,我实际上不再测试原始类。
如何正确完成? (我不想仅仅为这一个测试使用框架,所以有没有设计尝试以一种常见的方式解决这个问题?)
答案 0 :(得分:2)
许多人在开始测试驱动开发(或只是涉足其中)时面临的挑战之一是编写可测试的代码。这个概念背后的一个重要思想是编写模块化代码的离散单元,这些代码很容易测试并避免像上面那样的情况。
您不应在类定义中指定硬编码值,而应将其作为参数接受并将其传递到其他位置。如果需要此硬编码值,可以在类定义中将其指定为静态,并在生产使用中需要时将其作为参数传递。
public class HttpClient {
public final String DEFAULT_URL = "http://original.url.json";
public String readJsonDataFromUrl(String url) throws IOException {
URLConnection urlConnection = getUrlConnection(url);
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuffer content = new StringBuffer();
String readLine = "";
while ((readLine = reader.readLine()) != null) {
content.append(readLine);
}
return content.toString();
}
private URLConnection getUrlConnection(String url) throws IOException {
URL jsonLocator = new URL(url);
return jsonLocator.openConnection();
}
}
...常规用例
public class SomeClass {
public static void main(String[] args) {
HttpClient client = new HttpClient();
String result = client.readJsonDataFromUrl(HttpClient.DEFAULT_URL);
}
}
...并测试你的代码:
public class HttpClientTest {
// TODO expect exception
@Test
public void failDownload( ) {
HttpClient client = new HttpClient();
String result = client.readJsonDataFromUrl("wrong.url");
}
}
答案 1 :(得分:1)
Gilbert Le Blanc建议的另一个问题是,通过构造函数注入它,使HttpClient完全不知道URL。
public class HttpClient {
private final String url;
public HttpClient(String url) { this.url = url; }
}
您可以在HttpClient外部的某处硬编码URL(或从配置中读取),并在实例化客户端的任何地方注入它。然后在你的测试中,注入一个糟糕的网址将是微不足道的。
答案 2 :(得分:0)
感谢大家,但我认为Gilbert Le Blanc的解决方案最适合这种情况,如下所示:
原班级:
public class HttpClient {
private final String DOWNLOAD_URL = "http://my.original.json.url";
public String readJsonDataFromUrl() throws IOException {
URLConnection urlConnection = getUrlConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuffer content = new StringBuffer();
String readLine = "";
while ((readLine = reader.readLine()) != null) {
content.append(readLine);
}
return content.toString();
}
private URLConnection getUrlConnection() throws IOException {
URL jsonLocator = new URL(getConnectionString());
return jsonLocator.openConnection();
}
protected String getConnectionString() {
return DOWNLOAD_URL;
}
}
模拟对象:
public class HttpClientMock extends HttpClient {
private String downloadUrl = "http://my.original.json.url";
public HttpClientMock() {
super();
}
public HttpClientMock(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
@Override
protected String getConnectionString() {
return downloadUrl;
}
}
工作测试:
public class HttpClientTest {
private JSONParser jsonParser = new JSONParser();
@Test
public void readJsonDataFromUrlSucceeds() throws IOException, ParseException {
HttpClient httpClient = new HttpClientMock();
String jsonString = httpClient.readJsonDataFromUrl();
JSONObject jsonObject = (JSONObject)jsonParser.parse(jsonString);
assertTrue(jsonObject.size() > 0);
}
@Test(expected = IOException.class)
public void readJsonDataFromMalformedUrlFails() throws IOException, ParseException {
HttpClient httpClient = new HttpClientMock("http://malformed");
httpClient.readJsonDataFromUrl();
}
}