Модульное тестирование - расположение объектов, недоступных для тестового проекта - PullRequest
2 голосов
/ 06 октября 2010

Я пишу карточную игру в попытке изучить Silverlight.

У меня есть доменное представление игры, которая предоставляется Silverlight в качестве службы WCF, и единственный открытый метод для моего объекта Game - это «PlayCards». Какой метод выглядит следующим образом

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

Когда этот метод вызывается, объект Game обновляется и передается всем клиентам с использованием дуплексной привязки

Есть много сценариев, которые мне нужны для модульного тестирования (выполнение его через пользовательский интерфейс становится действительно утомительным, особенно учитывая, что существует множество сценариев, которые не часто встречаются в природе). Однако сделать это означало бы сделать доступными различные свойства, которые на самом деле не нужны (за исключением моих целей тестирования)

Вот некоторые особенности:

У меня есть игровой класс, который содержит список игроков:

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

 ...

В классе Игрока есть Рука

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

 ...

Класс Hand состоит из различных частей

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

Теперь в своих юнит-тестах я хочу установить различные сценарии, в которых у одного игрока есть определенные карты, а у следующего игрока есть определенные карты. Затем пусть первый игрок сыграет несколько карт (с помощью общедоступного метода PlayCards), а затем подтвердит состояние игры

Но, конечно, так как Players - это свойство только для чтения, я не могу этого сделать. Точно так же Рука (на Игроке) недоступна. Точно так же части руки.

Есть идеи, как мне это настроить?

Полагаю, это может означать, что мой дизайн неправильный?

спасибо

EDIT:

Вот как выглядят некоторые классы, чтобы дать вам представление о некоторых свойствах, на которые я хочу заявить после того, как карты были разыграны

Пока я экспериментирую с InternalsToVisible, я изменил частные сеттеры на внутренние, но меня интересует, как будет работать чистый TDD

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);
            }
        }

        internal 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; }

...

Ответы [ 3 ]

2 голосов
/ 06 октября 2010

Это не ваш дизайн, это неправильно, но ваша реализация может быть. Попробуйте это сделать с интерфейсами, имеющими только get уровней в объявлениях свойств:

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; }
}

Если вы строите свои классы таким образом, вы можете смоделировать руки, которые хотите протестировать, или построить игроков с этими руками программно, чтобы проверить логику на всех различных уровнях.

EDIT

Пример Mocked IPlayer (в данном случае используется Moq) для введения определенной (покерной) руки -

//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

РЕДАКТИРОВАТЬ 2

Я просто понял, почему Джефф прокомментировал, что он неправильно прочитал вопрос, и понял, что я ответил только на часть его. Чтобы расширить то, что вы заявили в комментариях, звучит так, что у вас есть что-то вроде этого:

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

Это не очень тестируемый класс, потому что ваши тесты ничего не могут установить. Лучший способ обойти это - создать пару интерфейсов IGame и ITestableGame, которые по-разному раскрывают свойства:

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; }
}

Затем, в вашем сервисе, просто отправьте клиентам объект IGame и либо сделайте интерфейс ITestableGame внутренним и выставьте его как InternalsVisibleTo(TestAssembly), либо просто сделайте его общедоступным и не используйте его вне вашего тестирование.

РЕДАКТИРОВАТЬ 3

На основании только того, что вы опубликовали, похоже, что ваш поток управления настроен примерно так:

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();
    }
}

Это довольно точно?

1 голос
/ 06 октября 2010

Я думаю, что суть проблемы здесь:

Есть много сценариев, которые мне нужны для модульного тестирования (выполнение этого через пользовательский интерфейс становится действительно утомительным, особенно потому, что существует множество сценариевэто не происходит, естественно, так часто). Однако сделать это будет означать обнародование различных свойств, которые на самом деле не нужны (за исключением моих целей тестирования).

Рекомендую не слишком беспокоиться об измененииинтерфейс для поддержки тестирования.

Во что бы то ни стало сохраняйте установленные методы доступа внутренними. * Вы можете ограничить влияние на свой API, поскольку все, что вам действительно нужно, - это дополнительные публичные конструкторы для объектов, которые вам нужно упорядочить.Например:

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

Вы хотите, чтобы вы могли написать простую и понятную контрольную информацию:

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

Я никогда не сожалел о том, что делал это, даже когда это простов целях тестирования.API все еще достаточно ясен, чтобы люди использовали правильные конструкторы: обычно они используют конструктор по умолчанию (или вообще не используют конструктор - вместо этого они вызывают game.AddPlayer("Christo")) и используют расширенный конструктор только для тестирования или десериализации (когдасоответствующий).

* И рассмотрите возможность изменения общедоступного интерфейса таких элементов, как Game.Players на IEnumerable<Player>, чтобы лучше передавать семантику только для чтения.

1 голос
/ 06 октября 2010

У меня была такая же проблема в прошлом.

Проверьте класс InternalsVisibleToAttribute .

Вот учебник о том, какиспользуйте его.

Кроме того, вы можете рассмотреть возможность изменения методов в ваших классах с private на protected.Затем вы можете написать подкласс с общедоступной функцией-оболочкой для вашего тестирования.Таким образом, вы получаете преимущества инкапсуляции и возможность легко тестировать существующий код с минимальными изменениями.

Как правило, если вам действительно нужно тестировать частные методы, что-то может потребовать некоторых изменений в вашем дизайне.,Я бы подумал о разделении функциональности между различными классами, чтобы ослабить связь.

Таким образом, у вас могут быть открытые методы в классах, которые делают определенные вещи и тестируют их по отдельности и как они взаимодействуют друг с другом.

Этот подход может быть более затратным, но я думаю, что он поможет в долгосрочной перспективе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...