单元测试返回非平凡对象的方法

时间:2009-06-24 14:37:09

标签: unit-testing

如何测试返回不允许访问其字段的复杂对象的方法。请考虑此代码段 -

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

我的问题很笼统,但我希望它能说明我是多么绝望地想要如何进行测试...感谢您的帮助

7 个答案:

答案 0 :(得分:11)

嗯,结果有什么重要意义?据推测,它可以做某事或者它将是一个无用的结果。在这种情况下,将工作代码与损坏代码区分开来的是什么?

(我不是要试图解决这个问题 - 有时真的很难保持紧密的封装,但要有效地测试。有时值得开放一个对象以使测试更容易......)

答案 1 :(得分:5)

这个对象做了什么?如果你不能查询字段等,我猜它会对你传入的某个对象起作用吗?如果是这种情况,你可以传入一个实现相同接口的虚拟对象,但检查结果对象是否使用适当的参数,按预期的顺序等调用它。?

e.g。

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

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

答案 2 :(得分:2)

首先,按照惯例,吸气剂不应该有副作用。这样做几乎肯定是一个坏主意

  

this.resultState = resultState;

在你的吸气器中。

那就是说,返回resultState必须有一些的目的,否则你为什么要返回呢?因此,请测试resultState是否执行了它应该做的事情。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

遗憾的是,您正在使用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的已知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类的后代,但提供对其父对象的内部值的公共访问,以便在单元测试中进行询问。

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

答案 5 :(得分:0)

如果您想要测试的只是在调用o.setResultState(resultState)之后,您可以使用o.getResultState()获取resultState,测试将非常简单:

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

答案 6 :(得分:0)

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

想象一下,不是新建ResultState,而是从工厂获得它,并且工厂提供了一些替代模拟ResultState的方法,以便您可以验证getResultState()是否正在执行创建它并返回它之间的ResultState对象。

获得该效果的一种简单方法是在可覆盖的方法上分解ResultState的创建。例如,

public ResultState getResultState() {
    ResultState resultState = createResultState();
    ...

protected ResultState createResultState() {
    return new ResultState();
}

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

虽然这有效,但编写测试是一种脆弱的方式。

另一种方法是走向像

这样的事情
public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

这要求您找出将ResultStateFactory注入对象的适当方法,这是依赖注入框架非常适合的。对于测试,请注入一个工厂,该工厂返回一个模拟的ResultState,该ResultState已经按照适当的期望进行了准备。这会将测试问题减少为“是getResultState()在返回之前正确操作ResultState对象吗?”