【问题标题】:unit testing a method that returns a non trivial object对返回非平凡对象的方法进行单元测试
【发布时间】:2009-06-24 14:37:09
【问题描述】:

如何测试返回一个不允许访问其字段的复杂对象的方法。考虑一下这个 sn-p--

public ResultState getResultState(){ 
  ResultState resultState = new ResultState();
  // logic //
  this.resultState = resultState; //set parent class field
  return resultState;
}

ResultState 是一个不允许其大部分方法和字段的对象。 我不知道如何测试这种方法,除了:

assertNotNull(object.getResultState()) //the return ResultState is used by other class methods

我的问题很笼统,但我希望它表明我是多么渴望得到有关如何继续测试的提示...感谢您的帮助

【问题讨论】:

    标签: unit-testing


    【解决方案1】:

    那么,结果有什么重要的呢?大概它可以做 something 或者这将是一个无用的结果。在这种情况下,如何区分工作代码和损坏代码?

    (我并不是想解决这个问题 - 有时保持紧密封装但有效测试确实非常困难。有时值得稍微打开一个对象以使测试更容易......)

    【讨论】:

    • 乔恩,好问题——结果有什么重要的。在考虑了这一点之后——关于结果的最重要的事情是它应该是一个对象,它应该是使用它的方法所期望的对象。这是在代码的 //LOGIC// 部分完成的,其中 LOGIC 操作“this”的字段 所以我做了以下测试场景: 1. 我为这个字段设置了 setter 和 getter 2. 用+ve(好值)和 -ve(坏值) 3. 毫无例外地构建它......这是微不足道的测试,但一个好的开始 - 谢谢
    【解决方案2】:

    这个对象有什么作用?如果您无法查询字段等,我猜它会作用于您传递给它的某个对象?如果是这种情况,您能否传入一个实现相同接口的虚拟对象,但检查结果对象是否使用适当的参数、按预期顺序等调用它?

    例如

    public class ResultObject {
       public void actOn(ActedUpon obj) {
          // does stuff
    

    其中ActedUpon 是一个接口,将有一个“正常”实现和一个验证实现(在这里使用像JMock 这样的模拟框架是理想的)

    【讨论】:

      【解决方案3】:

      首先,按照惯例,getter 不应该有副作用。所以几乎可以肯定,这样做是个坏主意

      this.resultState = 结果状态;

      在你的吸气剂中。

      也就是说,返回resultState必须有一些目的,否则你为什么要返回呢?所以测试 resultState 是否做了它应该做的。

      【讨论】:

        【解决方案4】:

        基于该代码。我会说检查 Not Null 就足够了。检查返回的 ResultState 的功能属于 ResultState 的测试。您正在尝试测试 getResultState() 的功能。它唯一要做的就是创建一个新对象并将对它的引用保存在 this.resultState 中。所以检查它是否创建了一个,看看它是否保存了引用。

        【讨论】:

          【解决方案5】:

          很遗憾,您正在使用 getResultState() 创建 ResultState 对象本身,这实际上是您的障碍。考虑重构为以下接口:

          protected ResultState initResultState ( )
          {
              this.resultState = new ResultState();
              // Initialization Logic...
              return this.resultState;
          } // END initResultState
          
          public ResultState getResultState ( )
          {
              if ( this.resultState === null )
              {
                  return this.initResultState();
              }
          
              return this.resultState;
          } // END getResultState()
          

          从这个位置,更容易测试你的 getter 方法。定义一个后代类,它返回一个已知的ResultState Stub for initResultState(),可以查询,即:

          /**
           * The test fixutre's initResultState() method merely returns a simple Stub of
           * the ResultState object, which can be more easily interrogated.
           */
          public ResultState initResultState ( )
          {
              return new Stub_ResultState();
          } // END initResultState
          

          当然,您仍然可以使用受保护的initResultState()。考虑以下重构:

          protected ResultState initResultState ( ResultState newResultState )
          {
              this.resultState = newResultState;
              // Initialization Logic...
              return this.resultState;
          } // END initResultState
          
          public ResultState getResultState ( )
          {
              if ( this.resultState === null )
              {
                  return this.initResultState( new ResultState() );
              }
          
              return this.resultState;
          } // END getResultState
          

          这允许您覆盖后代类中的getResultState() 方法,以便您可以传递另一个ResultState Stub 对象以在之后进行操作和询问,例如:

          /**
           * The test fixture's getResultState() method always acts as a passthrough for
           * initResultState(), which is protected, so that the output of the protected
           * method can be interrogated.
           */
          public ResultState getResultState ( )
          {
              return this.initResultState( new Stub_ResultState() );
          } // END getResultState
          

          从这个位置开始,您需要 两个 测试夹具:一个覆盖 initResultState() 的行为以测试 getter 的功能;另一个覆盖getResultState() 的行为以测试initResultState() 的行为。两者都使用Stub_ResultState 类的实例,它必然是ResultState 类的后代,但提供对其父对象内部值的公共访问,以便在单元测试中进行查询。

          我希望这是有道理的,但请随时要求澄清。

          【讨论】:

            【解决方案6】:

            如果你想测试的只是调用 o.setResultState(resultState) 后,可以通过 o.getResultState() 得到 resultState,那么测试就很简单了:

            ResultState resultState = new ResultState(...);
            o.setResultState(resultState);
            assertEquals(resultState, o.getResultState());
            

            【讨论】:

              【解决方案7】:

              通过在 getter 中执行新操作,您将致力于 ResultState 的具体实现。在您当前的情况下,这意味着实现对其状态不透明。

              想象一下,您从工厂获得了它,而不是在适当的位置新建 ResultState,并且工厂提供了一些方法来替换模拟 ResultState,以便您可以验证 getResultState() 正在使用创建和返回它之间的 ResultState 对象。

              获得这种效果的一种简单方法是将 ResultState 的创建分解为 on 可覆盖方法。例如,

              public ResultState getResultState() {
                  ResultState resultState = createResultState();
                  ...
              
              protected ResultState createResultState() {
                  return new ResultState();
              }
              

              然后,假设您的 Test 类在同一个包中,您可以创建一个覆盖 getResultState() 以返回模拟的内联子类。

              虽然这可行,但它是编写测试的一种脆弱方式。

              另一种方法是朝着类似的方向前进

              public ResultState getResultState() {
                  ResultState resultState = _resultStateFactory.newResultState();
                  ...
              

              这需要您找出将 ResultStateFactory 注入对象的适当方法,这是依赖注入框架非常适合的事情。对于测试,注入一个返回模拟 ResultState 的工厂,该工厂已经准备好适当的期望。这将测试问题简化为“getResultState() 在返回之前是否正确操作了 ResultState 对象?”

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-10-18
                • 1970-01-01
                • 1970-01-01
                • 2014-05-10
                • 1970-01-01
                • 2011-06-26
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多