О проблемах использования оператора new внутри класса, который будет протестирован модулем - PullRequest
2 голосов
/ 10 августа 2010

Введение

В настоящее время у меня есть что-то в форме:

Tetris class ---> FallingPiece class ----> Piece class

A Piece может быть Square, T и т. Д. Он имеет информацию о его форме и формах его вращения, его размере и т. Д.

Класс FallingPiece в основном содержит атрибут со ссылкой на Piece (в настоящее время падающая фигура в игре Тетрис) и, вероятно, будет иметь текущее местоположение (x, y) и цвет.

Мой первоначальный дизайн должен был иметь что-то вроде:

class Tetris {
    private IPieceGenerator pieceGenerator;
    private FallingPiece fallingPiece;
    ...

    public Tetris(IPieceGenerator pieceGenerator) {
        this.pieceGenerator = pieceGenerator;
        ...
    }

    private void someMethodThatNeedsAFallingPiece() {
        if (fallingPiece == null) {
            Piece piece = pieceGenerator.Generate();
            fallingPiece = new FallingPiece(piece);
        }

        ...
    }
}

У которого, конечно, есть проблема, что если я позже захочу провести модульное тестирование своего класса тетриса и узнать, в каком (x, y) местоположении платы находится мой текущий FallingPiece, я не могу. Я помню, как видел эту «проблему» в невероятных Миско Хевери «Чистые переговоры по коду» .

Первая проблема

похоже, что, поскольку я даю классу Tetris ответственность за создание FallingPiece объектов, я не могу тогда ссылаться на них (я прошел через конструкторское внедрение на фабрику Piece, а не FallingPiece Фабрика!).

Теперь я вижу как минимум 2 способа решить эту проблему:

  1. Я мог бы просто определить internal (C #) / package-protected (Java) геттер для атрибута FallingPiece, так что я могу легко проверить его. Это может показаться безобидным, но я не нахожу это слишком элегантным.
  2. Я мог бы вместо Piece Фабрики передать FallingPiece Фабрику. Затем я мог контролировать, какие объекты будут возвращены Фабрикой, и получить к ним доступ через нее. Что вы, ребята, думаете об этом? Это обычно используется?

Есть ли другой способ решения этой проблемы?

Есть вторая проблема

связано с тем фактом, что я изначально реализовал FallingPiece как неизменный тип. Это означает, что каждый раз, когда я хочу обновить позицию FallingPiece на плате Tetris, например, мне придется создавать новый экземпляр FallingPiece, а атрибут в Tetris теперь будет указывать на новый FallingPiece. Что может быть большой проблемой, если я хочу, например, иметь доступ к ссылке FallingPiece через FallingPieceFactory, который передается в класс Tetris. Мне кажется, что неизменные типы данных могут быть, если неправильно использовать много головной боли при попытке проверить классы, верно? Или это плохое использование неизменяемого типа данных, во-первых?

Спасибо

Ответы [ 3 ]

1 голос
/ 10 августа 2010

Я бы вставил только FallingPieceFactory, чтобы уменьшить связь Tetris с другими классами.Похоже, что он не знал о PieceFactory И FallingPieceFactory.

. Я бы ориентировал тестирование на использование класса Tetris.Ваш метод должен иметь заметный эффект.Как это используется?Есть ли класс, который использует данные Tetris для рисования игрового поля?Тогда все равно будет добытчик.Или картина называется классом Tetris?Затем он должен иметь ссылку на ответственный класс, и вы можете внедрить и наблюдать (возможно, издеваться) этот класс рисования.

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

1 голос
/ 10 августа 2010

Я бы сказал, что объект Tetris делает много:

protected void someMethodThatNeedsAFallingPiece() {
  if (fallingPiece == null) {
      IPiece piece = pieceGenerator.Generate();
      fallingPiece = fallingPieceGenerator.generate(piece);
  }
}

Этот метод имеет слишком много возможностей.

  1. Логика для проверки ненулевого значения fallingPiece.
  2. Генерация piece.
  3. Генерация нового fallingPiece.

Вы должны следовать «Последнему возможному моменту принятия решения».Избавься от всего этого. KISS .

Попробуйте:

protected void someMethodThatNeedsAFallingPiece() {
  fallingPiece = pieceModel.getFallingPiece();
}

Вставьте эту логику в pieceModel

1 голос
/ 10 августа 2010

Возможное решение:

  1. Вместо того, чтобы соединять реализации вместе, используйте интерфейсы.
  2. Внедрить генератор для создания FallingPiece

Таким образом, вы можете проверить все, используя фиктивные объекты для ваших интерфейсов. измените видимость someMethodThatNeedsAFallingPiece на protected, чтобы получить к нему доступ в своем тестовом примере или вызвать его с помощью отражения (зла).

public class Tetris {

    private IPieceGenerator pieceGenerator;
    private IFallingPiece fallingPiece;
    private IFallingPieceGenerator fallingPieceGenerator;

    public Tetris(IPieceGenerator pieceGenerator, IFallingPieceGenerator fallingPieceGenerator) {
      this.pieceGenerator = pieceGenerator;
      this.fallingPieceGenerator = fallingPieceGenerator;

    }

    protected void someMethodThatNeedsAFallingPiece() {
      if (fallingPiece == null) {
          IPiece piece = pieceGenerator.Generate();
          fallingPiece = fallingPieceGenerator.generate(piece);
      }

    }

}

Вот ваш юнит-тест:

public class TetrisTest {

    private IPieceGenerator pieceGeneratorMock;

    private IFallingPieceGenerator fallingPieceGeneratorMock;

    private IFallingPiece fallingPieceMock;

    private IPiece pieceMock;

    private Tetris tetris;



    @Before
    public void init() {
      this.pieceGeneratorMock = EasyMock.createMock(IPieceGenerator.class);
      this.fallingPieceGeneratorMock = EasyMock.createMock(IFallingPieceGenerator.class);
      this.fallingPieceMock = EasyMock.createMock(IFallingPiece.class);
      this.pieceMock = EasyMock.createMock(IPiece.class);
      this.tetris = new Tetris(pieceGeneratorMock, fallingPieceGeneratorMock);



    }

    @Test
    public void testSomeMethodThatNeedsAFallingPiece() {
      expect(pieceGeneratorMock.Generate()).andReturn(pieceMock);
      expect(fallingPieceGeneratorMock.generate(pieceMock)).andReturn(fallingPieceMock);
      replay(fallingPieceMock, fallingPieceGeneratorMock, pieceGeneratorMock, pieceMock);

      tetris.someMethodThatNeedsAFallingPiece();

      verify(fallingPieceMock, fallingPieceGeneratorMock, pieceGeneratorMock, pieceMock);

    }

}
...