4

我对 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),但我不确定如何在不公开它们的情况下对它们进行测试。

有没有普遍接受的方法来做到这一点?

干杯

斯图尔特

4

5 回答 5

1

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

编辑:

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

using System.Runtime.CompilerServices;

// making internals available to the testing project
[assembly: InternalsVisibleTo( "MyProject.IntegrationTests" )]
[assembly: InternalsVisibleTo( "MyProject.UnitTests" )]
于 2012-05-07T23:45:58.153 回答
0

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

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

这样我就可以测试公共API类,不依赖任何内部行为,如果内部行为发生变化,只会影响行为的测试;如果你不这样做,你会发现自己的测试很脆弱,公共 API 测试可能会因内部实现更改而中断。

于 2012-05-08T05:52:17.400 回答
0

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

还要考虑到您可能需要独立测试Deck它提供的服务Game,在这种情况下,最好通过Game的构造函数显式地将它作为对 Game 的依赖项提供。

这种方法还允许您依赖接口而不是实现,这有助于提供 to 的不同实现DeckGame进行模拟,或者允许您灵活地DeckGame将来提供 to 的替代实现。

例如

public interface IDeck{
    // provide the methods and properties that Game requires from Deck
}

public class PokerDeck:IDeck{
    // ... etc
}

public class TravelCardsDeck:IDeck{
    // ... etc
}

// [Test]
IDeck deck = new TravelCardsDeck();
var game = new Game(deck);
Assert.That(game.GetDeckSuitCount(),Is.EqualTo(4));
于 2012-05-07T15:58:50.710 回答
0

当然,您的卡片组的客户应该能够访问和遍历给定办公桌上的卡片,不是吗?即套牌应该发布类似IEnumerable<Card>.

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

于 2012-05-07T15:38:33.973 回答
0

在这种情况下,我不会立即关心内容。如果你对这个行为进行建模,你会得到一组卡片,这些卡片会变成一副牌(下面,aDeck需要一个IEnumerable<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);
于 2012-05-07T22:28:27.823 回答