为未暴露于UI的方法编写测试 - 需要一般帮助

时间:2012-05-07 15:27:37

标签: c# unit-testing tdd

我是TDD的新手,并且正在使用NUnit以C#书写卡片游戏应用程序的测试。

它的证明大多非常有用,但有一些我想测试的东西,但不希望公开任何公共属性。

例如,我希望能够测试一个新的卡片组由4个套装组成,每套套装有13张卡片。它可能看起来像:

[Test()]
public void ADeckOfCardsHas4Suits()
{
   Game game = new Game();
   Assert.AreEqual(4, game.Deck.SuitCount);
   Assert.AreEqual(13, game.Deck.GetCards(Suit.Spades).Count)
}

使用SuitCount返回类似的内容:

return this.cards.Select(c => c.Suit).Distinct().Count();

和GetCards(西装):

return this.cards.where(c => c.Suit == suit);

但是我不想将API中的这些属性或方法暴露给UI(我甚至可能不希望暴露Deck),但我不确定如何在不公开它的情况下对它们进行测试。

有没有普遍接受的方法呢?

干杯

斯图尔特

5 个答案:

答案 0 :(得分:1)

在这种情况下,我们经常使用(滥用?)朋友程序集来允许测试项目查看被测试类的内部成员。

编辑:

在您的主项目中添加一个FriendAssemblies.cs文件:

using System.Runtime.CompilerServices;

// making internals available to the testing project
[assembly: InternalsVisibleTo( "MyProject.IntegrationTests" )]
[assembly: InternalsVisibleTo( "MyProject.UnitTests" )]

答案 1 :(得分:0)

当然,你卡片组的客户应该能够访问并迭代给定桌面的卡片,不是吗?即套牌应该发布类似IEnumerable<Card>的内容。

如果是这样,您的单元测试可以抓住它并使用它来计算不同套装的数量。因此,方法SuitCountGetCardsOfSuit应该作为单元测试代码中的帮助程序实现。

答案 2 :(得分:0)

您应该测试班级的公共行为。如果UI与Game对话,并且需要知道有多少套装,而不是game.Deck.SuitCount测试game.GetDeckSuitCount()如果你有超过1个Deck重载,那么通过a指定哪个Deck的方法参数game.GetDeckSuitCount(2)。这种方式Deck隐藏在Game的客户端中并被封装。

还要考虑您可能需要独立测试Deck及其为Game提供的服务,在这种情况下,最好通过{{1}明确地将其作为依赖提供给Game。的构造函数。

此方法还允许您依赖于接口而不是实现,这有助于为模拟提供GameDeck的不同实现,或者允许您将来可以灵活地为Game提供Deck的替代实施。

e.g。

Game

答案 3 :(得分:0)

在这种情况下,我不会立即关心内容。如果你对行为进行建模,你会获得一组成为牌组的牌(下面是DeckIEnumerable<Card>)。然后你会把它们洗牌并解决它们。

public class Deck
{
    ...
    public Deck(IEnumerable<Card> cards) { ... Shuffle() ... }        
    public void DealTo(IDealable dealable, int numberOfCards) {...}
}

在这里,您可以创建一个简单的测试用例来处理一张卡:

var deck = new Deck(new Card[] { <somecard> });
var hand = new Hand(); 

deck.DealTo(hand, 1);

Assert.AreEqual(<somecard>, hand[0]);

...加上你能想到的任何测试。

然后你创建一个扑克牌组:

public class PokerDeck : Deck
{
    public PokerDeck() : base(<poker cards>)
}

// Pinochle, Uno, whatever 

所以为了测试PokerDeck,你可能会从这样的事情开始:

var deck = new PokerDeck();
IDealable testHand = new TestDealable();
deck.DealTo(testHand, 52);

//assert all are distinct
//assert all make up expected deck
//assert is shuffled

// test with multiple players / "dealables"

模拟德州扑克会是这样的:

// first card
foreach(var player in players)
   deck.DealTo(player, 1);

// second
foreach(var player in players)
   deck.DealTo(player, 1);

// wait for action

// flop
deck.DealTo(burnPile, 1);
deck.DealTo(board, 3);

// wait for action

// turn
deck.DealTo(burnPile, 1);
deck.DealTo(board, 1);

// wait for action

// river
deck.DealTo(burnPile, 1);
deck.DealTo(board, 1);

答案 4 :(得分:0)

正如所建议的那样,我使用InternalsVisibleTo attribut来允许测试程序集执行生产程序集的代码。

但是,我会将内部行为和实现提取到单独的内部类中。

这样,我可以测试公共API类,而不依赖于任何内部行为,如果内部行为发生变化,只会影响行为测试;如果你不这样做,你会发现自己有一些脆弱的测试,以及由于内部实现变化而可能破坏的公共API测试。