关于在将进行单元测试的类中使用 new 运算符的问题

发布于 2024-09-13 05:45:10 字数 1912 浏览 3 评论 0原文

目前

我有以下形式的东西:

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

Piece 可以是 SquareT 等。它包含有关其形状和形状的信息。它的旋转形状、大小等。

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 是,我不能。我记得在精彩的 Misko Hevery 的《整洁代码讲座》 中看到过这个“问题”。

第一个问题

似乎是,当我赋予 Tetris 类创建 FallingPiece 对象的责任时,我无法引用它们(我已经通过通过构造函数注入一个 Piece Factory,而不是 FallingPiece Factory!)。

现在,我至少可以看到两种方法来解决这个问题:

  1. 我可以为 FallingPiece 定义一个内部(C#)/包保护(Java)getter 属性,这样我就可以轻松测试它。这可能看起来无害,但我觉得它不太优雅。
  2. 我可以不传递一个 Piece 工厂,而是传递一个 FallingPiece 工厂。然后我可以控制工厂返回哪些对象并通过它访问它们。大家觉得这样怎么样?常用吗?

还有其他方法可以解决这个问题吗?

第二个问题

与我最初将 FallingPiece 实现为不可变类型这一事实相关。这意味着,例如,每次我想要更新 FallingPieceTetris 板上的位置时,我都必须创建一个新的 FallingPiece code> 实例和俄罗斯方块上的属性现在将指向一个新的 FallingPiece。例如,如果我想通过传递给 TetrisFallingPieceFactory 访问 FallingPiece 引用,这可能是一个大问题班级。在我看来,在尝试测试类时,如果滥用不可变数据类型可能会带来很多麻烦,对吧?或者这首先是对不可变数据类型的错误使用?

谢谢

Intro

Currently I have something of the form:

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

A Piece can be a Square, a T, etc. It has info about its shape and the shapes of its rotations, its size, etc.

The FallingPiece class basically contains an attribute with a reference to a Piece (the currently falling piece in the Tetris game) and will probably have its current (x, y) location and its color.

My initial design was to have something of the form:

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

        ...
    }
}

which of course has the problem that if I later want to Unit-Test my Tetris class and to know in which (x, y) location of the board my current FallingPiece is, I can't. I remember seeing this "problem" exposed in the fabulous Misko Hevery's The Clean Code Talks .

The first problem

seems to be that as I'm giving the Tetris class the responsability of creating FallingPiece objects, I can't then have a reference to them (I have passed through constructor injection a Piece Factory, not FallingPiece Factory!).

Now, I can see at least 2 ways to solve this:

  1. I could just define an internal(C#)/package-protected(Java) getter for the FallingPiece attribute, so I can easily test it. This might seem harmless, but I don't find it too elegant.
  2. I could instead of passing it a Piece Factory, pass a FallingPiece Factory. I could then control which objects would be returned by the Factory and access them through it. What do you guys think of this way? Is it commonly used?

Is there any other way of solving this?

There is a second problem

related to fact that I've originally implemented a FallingPiece to be an immutable type. This means that every time I want to update the FallingPiece's position on the Tetris board, for example, I'll have to create a new FallingPiece instance and the attribute on Tetris will now point to a new FallingPiece. Which can be a big problem if I want, for example, to have access to the FallingPiece reference through a FallingPieceFactory that is passed to the Tetris class. It appears to me that immutable data types can be if misused a lot of headache when trying to test classes, right? Or is this a bad use of an immutable datatype, in the first place?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

治碍 2024-09-20 05:45:10

可能的解决方案:

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

    }

}

Possiblle solution:

  1. Instead of wiring implementations together, use interfaces.
  2. Intruduce a generator for creating FallingPiece

This way you can test everything using mock objects for your interfaces. change visibility of someMethodThatNeedsAFallingPiece to protected to access it in your test case or call it using reflection (evil).

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

    }

}

Here's your unit test:

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

    }

}
最近可好 2024-09-20 05:45:10

我只会注入一个 FallingPieceFactory 来减少俄罗斯方块与其他类的耦合。看起来它不知道 PieceFactoryFallingPieceFactory

我会将测试定位于俄罗斯方块类的使用。你的方法必须有明显的效果。它是如何使用的?是否有一个类使用俄罗斯方块的数据来绘制游戏领域?那么无论如何都会有一个吸气剂。或者这幅画是由 Tetris 类调用的?然后它必须引用负责的类,您可以注入并观察(可能是模拟)这个绘画类。

关于不可变性:我在测试不可变对象时从未遇到过任何问题。如果 FallingPiece 是不可变的良好候选者似乎值得怀疑,因为主要目的似乎是掉落(意味着:改变其位置)。

I would inject only a FallingPieceFactory to reduce the coupling of Tetris to other classes. It looks like that it havn't to know about the PieceFactory AND the FallingPieceFactory.

I would orientate the testing on the usage of the Tetris class. Your method must have an observable effect. How is it used? Is there a class that uses the data of Tetris to paint the game field? Then there would be a getter anyway. Or is the painting called by the Tetris class? Then it have to had a reference to the responsible class and you can inject and observe (possible mock) this painting class.

Regarding immutibility: I never experienced any problems with testing immutable objects. If FallingPiece is a good candidate to be immutable seems questionable as the main purpose seems to be fall down (means: change its position).

夜吻♂芭芘 2024-09-20 05:45:10

我想说的是,俄罗斯方块对象做得太多了:

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

这个方法有太多的责任。

  1. 验证非空 fallingPiece 的逻辑。
  2. 生成片段
  3. 生成新的fallingPiece

您应该遵循“最后可能的决定时刻”。摆脱这一切。 接吻

试试这个:

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

将该逻辑推入 pieceModel

I would say that the Tetris object is doing to much:

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

This method has too many reponsibilities.

  1. Logic to validate non-null fallingPiece.
  2. Generate piece.
  3. Generate new fallingPiece.

You should be following the 'Last Possible Moment of Decision`. Get rid of all that. KISS.

Try this:

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

Push that logic into the pieceModel

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文