【问题标题】:How to write a jUnit test for a class that uses a network connection如何为使用网络连接的类编写 jUnit 测试
【发布时间】:2011-02-13 00:29:46
【问题描述】:

我想知道使用 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 ();
        }
    }

}

【问题讨论】:

    标签: java unit-testing tdd junit


    【解决方案1】:

    Stubbing

    您需要一个测试替身(存根)来进行独立、简单的单元测试。以下内容未经测试,但演示了该想法。使用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

    【讨论】:

    • 我正要输入一些非常相似的东西。我认为他需要像你提到的那样将他的课程与 url 连接分离。然后他的单元测试将测试与模拟 urlconnection 的交互。他还需要一个集成测试来测试与 url 连接的具体实例的集成。
    • 我应该提到嘲笑是一个超载的术语。从本质上讲,这一切都归结为使用假对象来启用隔离测试,请在此处查看 SO 上的其他模拟问题以获得更广泛的概述。如果有人告诉你像我一样使用模拟,你可以使用假/存根/模拟/手卷对象。
    • +1,这是一个比公认的答案更通用、更适用的解决方案。
    • @Grundlefleck 我同意,但同时接受的答案是一个完全有效的解决方案。事实上,我建议在单元测试时开始测试扩展而不是模拟框架。一旦测试扩展变得痛苦/乏味 - 是时候转向模拟框架了。
    • 大声笑,当嘲笑变得累人且成本高昂时,@Finglas 该怎么办? :)
    【解决方案2】:

    可能的解决方案:您可以扩展这个类,覆盖 callWebsite(为此目的,您必须对其进行保护) - 并且覆盖方法编写一些存根方法实现。

    【讨论】:

      【解决方案3】:

      从稍微不同的角度来看待事物...

      我不太担心测试这个特定的类。其中的代码非常简单,虽然确保它与连接一起工作的功能测试会有所帮助,但单元级测试“可能”不是必需的。

      相反,我会专注于测试它调用的方法,这些方法看起来确实在做某事。具体...

      我将从这一行测试constructURL方法:

      String url = constructURL (event);
      

      确保它可以从不同的事件中正确构造一个 URL,并在应该抛出异常时(可能在无效事件或 null 上)。

      我将从以下行测试该方法:

      String responseMessage = responseParser.getResponseMessage (connection);
      

      可能将任何“从连接中获取信息”逻辑提取到一个过程中,并在原始过程中只留下“解析所述信息”:

      String responseMessage = responseParser.getResponseMessage(responseParser.getResponseFromConnection(connection));
      

      或类似的东西。

      想法是将任何“必须处理外部数据源”的代码放在一个方法中,并将任何代码逻辑放在可以轻松测试的单独方法中。

      【讨论】:

        【解决方案4】:

        作为 Finglas 关于模拟的有用答案的替代方案,请考虑一种存根方法,其中我们覆盖 callWebsite() 的功能。这在我们对 callWebsite 的逻辑不像在 pushEvent() 中调用的其他逻辑那么感兴趣的情况下非常有效。要检查的一件重要事情是 callWebsite 是用正确的 URL 调用的。因此,首先将 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);
            }
         }
        

        【讨论】:

          【解决方案5】:

          创建一个抽象类 WebsiteCaller,它是 ConcreteWebsiteCallerWebsiteCallerStub 的父类。

          这个类应该有一个方法callWebsite (String url)。将您的 callWebsite 方法从 MyClass 移动到 ConcreteWebsiteCallerMyClass 看起来像:

          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;
              }
          }
          

          并以适合测试的方式在您的WebsiteCallerStub 中实现方法callWebsite

          然后在你的单元测试中做这样的事情:

          @Test
          public void testPushEvent() {
             MyClass mc = new MyClass (new WebsiteCallerStub());
             mc.pushEvent (new Event(...));
          }
          

          【讨论】:

          • 我认为您展示的是集成测试,因为您的测试依赖于外部资源。更好的单元测试将注入一个模拟/存根网站调用程序接口。您的集成测试可以专门测试 websitecaller 类以及注入 Mycalss 的 websitecaller
          猜你喜欢
          • 2021-11-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-03
          • 2022-11-04
          • 2015-08-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多