2010-04-12 55 views
1

我使用Rhino Mocks尝试验证当我调用某个方法时,该方法反过来将正确地分组项目,然后调用另一种方法。检查参数是否正确使用的最佳测试模式是什么?

事情是这样的:

//Arrange 
var bucketsOfFun = new BucketGame(); 

var balls = new List<IBall> 
       { 
        new Ball { Color = Color.Red }, 
        new Ball { Color = Color.Blue }, 
        new Ball { Color = Color.Yellow }, 
        new Ball { Color = Color.Orange }, 
        new Ball { Color = Color.Orange } 
       }; 

//Act 
bucketsOfFun.HaveFunWithBucketsAndBalls(balls); 

//Assert ??? 

这里就是麻烦的开始我。我的方法是做这样的事情:

public void HaveFunWithBucketsAndBalls(IList<IBall> balls) 
{ 
    //group all the balls together according to color 
    var blueBalls = GetBlueBalls(balls); 
    var redBalls = GetRedBalls(balls); 
    // you get the idea 

    HaveFunWithABucketOfBalls(blueBalls); 
    HaveFunWithABucketOfBalls(redBalls); 
    // etc etc with all the different colors 
} 

public void HaveFunWithABucketOfBalls(IList<IBall> colorSpecificBalls) 
{ 
    //doing some stuff here that i don't care about 
    //for the test i'm writing right now 
} 

我想断言的是,每次我打电话说我有一组1个红球,那么1个蓝色球调用它HaveFunWithABucketOfBalls,然后1个黄球,然后2个橙色的球。

如果我可以断言该行为,那么我可以验证该方法正在做我想做的事情,即正确地分组这些球。

任何想法是什么样的最好的测试模式?

+0

只是为了方便,为什么你测试你的方法调用另一种方法?据推测,你应该只是测试你的对象的公共方法,并断言你得到正确的输出,你不应该测试实际的内部实现。 – Juliet 2010-04-12 23:15:18

+0

@Juliet这是一个很好的观点,答案是因为这两种方法都是公开的。在我的实际实现中,它们都可以独立使用。所以两者之间唯一真正的区别是对象如何从我在这里描述的方法组织起来,这就是为什么我想测试这个功能。 – Joseph 2010-04-13 17:16:51

回答

2

一种方法是打出来的责任与颜色特定的球一起工作的依赖,例如,IBucketHandler

//Arrange 
var bucketHandler = MockRepository.GenerateStub<IBuckerHandler>(); 
var bucketsOfFun = new BucketGame(bucketHandler); 
... 
//Assert 
bucketHandler.AssertWasCalled(x => x.HaveFunWithABucketOfBalls(redBalls)); 
bucketHandler.AssertWasCalled(x => x.HaveFunWithABucketOfBalls(greenBalls)); 

那么这个测试是检查你BucketGame正确调用HaveFunWithABucketOfBalls的嘲笑对象。这可能仍然会在指定每个参数应该是什么时遇到麻烦。反过来,通过将排序球的责任推给新的依赖关系,您可以更容易地进行测试(以引入更多抽象为代价)。那么你最终的东西是这样的:

//Arrange 
var balls = new List<IBall>(); //Can really be anything, we just need the object reference 
var greenBalls = new List<IBall>(); 
var redBalls = new List<IBall>(); 
var sortedBalls = new [] { greenBalls, redBalls }; 

var bucketHandler = MockRepository.GenerateStub<IBucketHandler>(); 
var ballSorter = MockRepository.GenerateStub<IBallSorter>(); 
ballSorter.Stub(x => x.Sort(balls)).Return(sortedBalls); 

var bucketsOfFun = new BucketGame(bucketHandler, ballSorter); 

//Act 
bucketsOfFun.HaveFunWithBucketsAndBalls(balls); 

//Assert 
bucketHandler.AssertWasCalled(x => x.HaveFunWithABucketOfBalls(greenBalls)); 
bucketHandler.AssertWasCalled(x => x.HaveFunWithABucketOfBalls(redBalls)); 

而且通过这个测试,在BucketGame

public BucketGame(IBucketHandler bucketHandler, IBallSorter ballSorter) 
{ 
    this.bucketHandler = bucketHandler; 
    this.ballSorter = ballSorter; 
} 

public void HaveFunWithBucketsAndBalls(IList<IBall> balls) 
{ 
    //group all the balls together according to color 
    var sortedBalls = ballSorter.Sort(balls); 
    foreach (var groupOfBalls in sortedBalls) 
    { 
     bucketHandler.HaveFunWithABucketOfBalls(groupOfBalls); 
    } 
} 

此测试BucketGame逻辑。您现在要为IBallSorter实现编写单元测试,以检查它是否按照需要按颜色对球进行排序。这些测试可能不需要任何嘲讽,你只需要能够抛出数据,并断言你获得的数据是你期望的。

+0

谢谢大卫。我认为这对我来说可能是最好的方法。有趣的是,我真正的实现类似于这种构造,我只需要考虑一个名为BucketHandler的抽象名称。 – Joseph 2010-04-13 17:23:26

+0

一个问题,如果BucketHandler是一个存根或一个模拟,因为你断言该对象已经做了一些事情,这让我认为它应该是一个模拟,而不是一个存根,但在你的例子中,你打电话MockRepository.GenerateStub (); ? – Joseph 2010-04-13 17:26:20

+0

@joseph - 将它作为存根是我习惯的一种习惯。我几乎从不使用GenerateMock ,因为我通常希望我的属性作为实际属性(例如myStub.SomeProp = 10;)工作。在这种情况下,您可以用GenerateMock 替换它,它仍然可以工作,并且可能更符合语义。 – 2010-04-13 21:45:33

0

你需要在任何测试中挑出的第一件事就是你的受试者是什么。这可能听起来没有道理,但我从我自己的经验中知道,当你学习交互测试时,这可能会令人困惑。

我猜测这是问题的一部分的原因是你有一个IBall对象,但是你想要声明一个方法看起来并不是接口的一部分。正如你所知道的,犀牛通过重写虚拟的东西来实现它,比如一个界面;所以如果你想用Rhino来做到这一点,你必须给它一个接口。假如是这样的话,也许

interface IBucketGame{ 
    void HaveFunWithBucketsAndBalls(IList<IBall> balls) 
    void HaveFunWithSpecificBalls(IList<IBall> balls) 
} 

现在,您可以建立一个互动测试:

[Test] 
public void HaveFunWithBucket_IfMoreThanOneColor_CallsHaveFunWithSpecificBallsForSpecificColor() 
    { 
     //Arrange 
     var bucketsOfFun = MockRepository.GenerateMock<IBucketGame>(); 

     IBall expColor_1 = new Ball(Color.Red); 
     IBall expColor_2 = new Ball(Color.Green); 
     IBall expColor_3 = new Ball(Color.Red); 
     var startlist = new List<IBall>{expColor_1, expColor_2, expColor_3}; 
     var onlyRedBalls = new List<IBall>{expColor_1, expColor_3}; 
     var onlyGreenBalls = new List<IBall>{expColor_2}; 

     //Act 
     bucketsOfFun.HaveFunWithBucketsAndBalls(balls); 

     //Assert 
     bucketsOfFun.AssertWasCalled(x=>x.HaveFunSpecificBalls(Arg<IEnumerable<IBall>>.List.Equal(onlyRedBalls))); 
     bucketsOfFun.AssertWasCalled(x=>x.HaveFunSpecificBalls(Arg<IEnumerable<IBall>>.List.Equal(onlyGreenBalls))); 

    } 

HTH,
Berryl测试这个

+0

@Berryl然后,你如何测试,调用bucketsOfFun.HaveFunWithBucketsAndBalls(球)实际上什么都做。你只是嘲笑一个没有定义实现的接口。我试图测试一个具体的实现,所以我有点失去你在那里得到的东西。 – Joseph 2010-04-12 22:52:49

+0

这是一个无意义的测试,但这就是你当前的规格要求!我以为你只是想看看犀牛的一些机制,所以你可以开始思考一个逻辑用例,这将导致逻辑​​测试... – Berryl 2010-04-13 00:12:53

+0

没有我问的问题只是一个代理真实存在的问题。实际上我有一个用例想要做到这一点,我无法弄清楚测试它的方法。所以为了澄清,我的问题不是假设,而是我试图克服的一个实际问题。 – Joseph 2010-04-13 17:18:21

相关问题