我想知道使用jUnit测试在下面的类中测试方法“pushEvent()”的最佳方法是什么。 我的问题是,私有方法“callWebsite()”总是需要连接到网络。我怎样才能避免这个要求或重构我的课程,我可以在没有连接到网络的情况下测试它?
class MyClass {
public String pushEvent (Event event) {
//do something here
String url = constructURL (event); //construct the website url
String response = callWebsite (url);
return response;
}
private String callWebsite (String url) {
try {
URL requestURL = new URL (url);
HttpURLConnection connection = null;
connection = (HttpURLConnection) requestURL.openConnection ();
String responseMessage = responseParser.getResponseMessage (connection);
return responseMessage;
} catch (MalformedURLException e) {
e.printStackTrace ();
return e.getMessage ();
} catch (IOException e) {
e.printStackTrace ();
return e.getMessage ();
}
}
}
答案 0 :(得分:27)
您需要一个测试双(存根)才能进行隔离,简单的单元测试。以下是未经测试,但演示了这个想法。使用Dependency Injection将允许您在测试时注入HttpURLConnection的测试版本。
public class MyClass()
{
private IHttpURLConnection httpUrlConnection;
public MyClass(IHttpURLConnection httpUrlConnection)
{
this.httpUrlConnection = httpUrlConnection;
}
public String pushEvent(Event event)
{
String url = constructURL(event);
String response = callWebsite(url);
return response;
}
}
然后,您创建一个存根(有时称为模拟对象)作为具体实例的替代。
class TestHttpURLConnection : IHttpURLConnection { /* Methods */ }
您还将构建一个具体版本,供您的生产代码使用。
class MyHttpURLConnection : IHttpURLConnection { /* Methods */ }
使用您的测试类(adapter),您可以指定测试期间应该发生的事情。模拟框架将使您能够使用更少的代码执行此操作,或者您可以手动连接它。测试的最终结果是,您将设置对测试的期望,例如,在这种情况下,您可以将OpenConnection设置为返回一个真正的布尔值(顺便说一下,这只是一个例子)。然后,您的测试将断言当此值为true时,PushEvent方法的返回值与某些预期结果匹配。我暂时没有正确地触及Java,但这里有一些由StackOverflow成员指定的recommended mocking frameworks。
答案 1 :(得分:9)
可能的解决方案:您可以扩展此类,覆盖callWebsite(您必须为此目的保护它) - 并且覆盖方法编写一些存根方法实现。
答案 2 :(得分:7)
从略微不同的角度处理事情......
我不太担心测试这个特定的课程。其中的代码非常简单,虽然确保使用连接的功能测试会有所帮助,但单元级测试“可能”不是必需的。
相反,我专注于测试它所调用的实际上做某事的方法。具体地说...
我从这一行测试了constructURL方法:
String url = constructURL (event);
确保它可以从不同的事件正确构造一个URL,并在应该的时候抛出异常(可能是在一个无效的事件或null)。
我将从以下行测试该方法:
String responseMessage = responseParser.getResponseMessage (connection);
可能将任何“从连接中获取信息”逻辑拉出到一个proc中,并且只留下原始的“解析所述信息”:
String responseMessage = responseParser.getResponseMessage(responseParser.getResponseFromConnection(connection));
或类似的东西。
想法是在一个方法中放置任何“必须处理外部数据源”代码,并且在可以轻松测试的单独方法中放置任何代码逻辑。
答案 3 :(得分:4)
作为Finglas关于模拟的有用答案的替代方案,考虑一种我们覆盖callWebsite()功能的存根方法。在我们对callWebsite的逻辑不像pushEvent()中调用的其他逻辑那样感兴趣的情况下,这非常有效。要检查的一件重要事情是使用正确的URL调用callWebsite。因此,首先更改为callWebsite()的方法签名变为:
protected String callWebsite(String url){...}
现在我们创建一个这样的存根类:
class MyClassStub extends MyClass {
private String callWebsiteUrl;
public static final String RESPONSE = "Response from callWebsite()";
protected String callWebsite(String url) {
//don't actually call the website, just hold onto the url it was going to use
callWebsiteUrl = url;
return RESPONSE;
}
public String getCallWebsiteUrl() {
return callWebsiteUrl;
}
}
最后在我们的JUnit测试中:
public class MyClassTest extends TestCase {
private MyClass classUnderTest;
protected void setUp() {
classUnderTest = new MyClassStub();
}
public void testPushEvent() { //could do with a more descriptive name
//create some Event object 'event' here
String response = classUnderTest.pushEvent(event);
//possibly have other assertions here
assertEquals("http://some.url",
(MyClassStub)classUnderTest.getCallWebsiteUrl());
//finally, check that the response from the callWebsite() hasn't been
//modified before being returned back from pushEvent()
assertEquals(MyClassStub.RESPONSE, response);
}
}
答案 4 :(得分:2)
创建一个抽象类WebsiteCaller
,它将是ConcreteWebsiteCaller
和WebsiteCallerStub
的父级。
这个类应该有一个方法callWebsite (String url)
。将您的callWebsite方法从MyClass
移至ConcreteWebsiteCaller
。 MyClass
看起来像是:
class MyClass {
private WebsiteCaller caller;
public MyClass (WebsiteCaller caller) {
this.caller = caller;
}
public String pushEvent (Event event) {
//do something here
String url = constructURL (event); //construct the website url
String response = caller.callWebsite (url);
return response;
}
}
并以某种适合测试的方式在callWebsite
中实施方法WebsiteCallerStub
。
然后在你的单元测试中做这样的事情:
@Test
public void testPushEvent() {
MyClass mc = new MyClass (new WebsiteCallerStub());
mc.pushEvent (new Event(...));
}