开发者

Unit Testing - Arranging objects that are inaccessible to the test project

I am writing a card game in an attempt to learn Silverlight.

I have domain representation of the game which is exposed to Silverlight as a WCF service and the only public method on my Game object is "PlayCards". Which is a method that looks like this

public void PlayCards(Guid playerId, List<Card> cards)

When this method is invoked, the Game object is updated and transmitted to all the clients using a duplex binding

There are lots of scenarios I need to unit test (doing it via the UI is becoming really tedious, especially as there are lots of scenarios that don't occur naturally that often). However, to do so would mean making various properties public that really don't need to be (except for my testing purposes)

Here's some specifics:

I have a game class that contains a list of players :

public class Game
{       
 public List<Player> Players
 {
    get { return _players; }
 }

 ...

The Player class has a Hand

 public class Player
 {
    public Hand Hand { get; internal set; }

 ...

The Hand class has various parts

public class Hand
    {
        public List<Card> FaceDownCards { get; internal set; }
        public List<Card> FaceUpCards { get; internal set; }
        public List<Card> InHandCards { get; internal set; }

Now, in my unit tests I want to set up various scenarios where one player has certain cards, and the next player has certain cards. Then have the first player play some cards (via the public PlayCards method) and then Assert on the game state

But of course, as Players is a read only property I can't do this. Likewise Hand (on Player) is inaccessible. Likewise the parts of the hand.

Any ideas how I can set this up?

I guess this might mean my design is wrong?

thanks

EDIT:

Here's what some classes look like to give you an idea of some of the properties I want to Assert on after cards have been played

I have changed the private setters to internal for the moment while I experiment with InternalsToVisible, but am interested what the pure TDD way of doing things would be

public class Game
    {
        public List<Player> Players { get; internal set; }
        public Deck Deck { get; internal set; }
        public List<Card> PickUpPack { get; internal set; }
        public CardList ClearedCards { get; internal set; }

        public Player CurrentPlayer
        {
            get
            {
                return Players.SingleOrDefault(o => o.IsThisPlayersTurn);
            }
        }

        in开发者_开发百科ternal Direction Direction { get; set; }

..

public class Player
    {
        public Guid Id { get; internal set; }
        public Game CurrentGame { get; internal set; }
        public Hand Hand { get; internal set; }
        public bool IsThisPlayersTurn { get; internal set; }
        public bool IsAbleToPlay { get; internal set; }
        public string Name { get; internal set; }
...

 public class Hand
    {
        public List<Card> FaceDownCards { get; internal set; }
        public List<Card> FaceUpCards { get; internal set; }
        public List<Card> InHandCards { get; internal set; }

...


It's not your design that's wrong, but your implementation might be. Try this, with the interfaces only having getters on the property declarations:

public class Game : IGame
{       
    public List<IPlayer> Players
    {
        get { return _players; }
    }
}

public class Player : IPlayer
{
    public IHand Hand { get; internal set; }
}

public class Hand : IHand
{
    public List<Card> FaceDownCards { get; internal set; }
    public List<Card> FaceUpCards { get; internal set; }
    public List<Card> InHandCards { get; internal set; }
}

If you build your classes this way, you can mock up the hands you want to test, or build players with those hands programatically, to validate the logic at all the different levels.

EDIT

An example of a Mocked IPlayer (using Moq in this case) to inject a specific (poker) hand -

//Mmmm...Full House
List<Card> inHandCards = new List<Card> { ...Cards...}; //2 Kings?
List<Card> faceDownCards = new List<Card> { ...Cards...}; 
List<Card> faceUpCards = new List<Card> { ...Cards...};  //3 4s?

Mock<IHand> hand = new Mock<IHand>();
hand.SetupGet(h => FaceDownCards).Returns(faceDownCards);
hand.SetupGet(h => FaceUpCards).Returns(faceUpCards);
hand.SetupGet(h => InHandCards).Returns(inHandCards);

Mock<IPlayer> player = new Mock<IPlayer>();
player.SetupGet(p => p.Hand).Returns(hand.Object);

//Use player.Object in Game

EDIT 2

I just realized why Jeff commented that he had misread the question, and realized that I'd only answered part of it. To expand on what you stated in the comments, it sounds like you have something like this:

public class Game
{
    public Player NextPlayer { get; private set; }
    public bool NextPlayerCanPlay { get; private set; }
    ...
}

Which is not a very testable class because your tests can't set anything. The best way I've found to get around this is to build a couple of interfaces IGame and ITestableGame which expose the properties differently:

public interface IGame
{
    IPlayer NextPlayer { get; }
    bool NextPlayerCanPlay { get; }
}

internal interface ITestableGame
{
    IPlayer NextPlayer { get; set; }
    bool NextPlayerCanPlay { get; set; }
}

public class Game : IGame, ITestableGame
{
    public IPlayer NextPlayer { get; set; }
    public bool NextPlayerCanPlay { get; set; }
}

Then, in your service, just send the clients an IGame object, and either make the ITestableGame interface internal and market it as InternalsVisibleTo(TestAssembly), or just make it public and don't use it outside of your testing.

EDIT 3

Based only on what you posted, it sounds like your control flow is set up something like this:

public class Player 
{
    public void Play(List<Card> cardsToPlay) 
    {
        hand.InHandCards.Remove(cardsToPlay);
        currentGame.PickUpPack.Add(cardsToPlay);
        hand.Add(currentGame.Deck.Draw(cardsToPlay.Count));
        currentGame.SelectNextPlayer();
    }
}

Is that fairly accurate?


I think the crux of the problem is here:

There are lots of scenarios I need to unit test (doing it via the UI is becoming really tedious, especially as there are lots of scenarios that don't occur naturally that often). However, to do so would mean making various properties public that really don't need to be (except for my testing purposes).

I recommend not worrying too much about changing the interface to support testing.

By all means keep set accessors internal.* You can limit the impact on your API, since all you really need are additional public constructors for the objects you need to arrange. For example:

public Player(Guid id, Hand hand, bool isThisPlayersTurn, string name) { ... }

You want to get to the point where you can write simple, clear test cases:

Hand   hand   = new Hand(faceDownCards, faceUpCards, inHandCards);
Player player = new Player(Guid.NewGuid(), hand, true, "Christo");
Game   game   = new Game(player);

I've never regretted doing this, even when it's just for testing purposes. The API is still clear enough that people use the correct constructors: normally, they'd use a default constructor (or no constructor at all - instead they'll call game.AddPlayer("Christo")) and only use the extended constructor for testing or deserialization (when appropriate).

* And consider changing the public interface of members like Game.Players to IEnumerable<Player> to better communicate the read-only semantics.


I've had the same problem in the past.

Check out the InternalsVisibleToAttribute class.

Here's a tutorial on how to use it.

Also, you can consider changing the methods in your classes from private to protected. Then, you could write a subclass with a public wrapper function for your testing. This way, you get the benefits of encapsulation and the ability to easily test your existing code with minimal changes.

As a rule of thumb, if you really need to test private methods something may need some tweaking in your design. I'd think about separating functionality across different classes to loosen up coupling.

This way, you can have public methods in classes that do specific things and test those individually and how they interact with each other.

This approach might be more overhead, but I think it will help in the long run.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜