如何在测试驱动开发中避免访问器?

2022-09-03 15:28:01

我正在学习测试驱动开发,我注意到它强制松散耦合的对象,这基本上是一件好事。但是,这有时也会迫使我为我通常不需要的属性提供访问器,我认为SO上的大多数人都同意访问器通常是设计不良的标志。在做TDD时,这是不可避免的吗?

下面是一个示例,即没有 TDD 的实体的简化绘制代码:

class Entity {
    private int x;
    private int y;
    private int width;
    private int height;

    void draw(Graphics g) {
        g.drawRect(x, y, width, height);
    }
}

实体知道如何绘制自己,这很好。一切尽在一处。但是,我正在做TDD,所以我想检查我的实体是否被我将要实现的“fall()”方法正确移动。测试用例如下所示:

@Test
public void entityFalls() {
    Entity e = new Entity();
    int previousY = e.getY();
    e.fall();
    assertTrue(previousY < e.getY());
}

我必须查看对象的内部(嗯,至少在逻辑上)状态,看看位置是否正确更新。由于它实际上在路上(我不希望我的测试用例依赖于我的图形库),我将绘图代码移动到类“Renderer”中:

class Renderer {
    void drawEntity(Graphics g, Entity e) {
        g.drawRect(e.getX(), e.getY(), e.getWidth(), e.getHeight());
    }
}

松散耦合,很好。我甚至可以将渲染器替换为以完全不同的方式显示实体的渲染器。但是,我必须公开实体的内部状态,即其所有属性的访问器,以便呈现器可以读取它。

我觉得这是TDD特别强迫的。我该怎么办?我的设计可以接受吗?Java是否需要C++中的“朋友”关键字?

更新:

感谢您到目前为止的宝贵意见!但是,我担心我选择了一个不好的例子来说明我的问题。这完全是编造的,我现在将演示一个更接近我的实际代码:

@Test
public void entityFalls() {
    game.tick();
    Entity initialEntity = mockRenderer.currentEntity;
    int numTicks = mockRenderer.gameArea.height
                   - mockRenderer.currentEntity.getHeight();
    for (int i = 0; i < numTicks; i++)
        game.tick();
    assertSame(initialEntity, mockRenderer.currentEntity);
    game.tick();
    assertNotSame(initialEntity, mockRenderer.currentEntity);
    assertEquals(initialEntity.getY() + initialEntity.getHeight(),
                 mockRenderer.gameArea.height);
}

这是一个基于游戏循环的游戏实现,其中实体可能会崩溃,其中包括。如果它撞到地面,则会创建一个新实体。

“mockRenderer”是接口“Renderer”的模拟实现。这种设计部分是由TDD强制的,但也因为我要用GWT编写用户界面,并且(尚未)在浏览器中没有显式绘图,因此我认为Entity类不可能承担该责任。此外,我希望将来保留将游戏移植到本机Java / Swing的可能性。

更新 2:

再想一想,也许没关系。也许实体和绘图是分开的,并且实体告诉其他对象足够多的关于它自己的东西来绘制是可以的。我的意思是,我还能如何实现这种分离呢?我真的看不出没有它该如何生活。即使是伟大的面向对象程序员有时也会使用带有 getters/setter 的对象,特别是对于像实体对象这样的东西。也许 getter/setter 并不全是邪恶的。你觉得怎么样?


答案 1

务实的程序员讨论告诉,不要问。你不想知道实体,你希望它被绘制。告诉它在给定的图形上绘制自己。

您可以重构上面的代码,以便实体进行绘制,如果实体不是矩形而是实际上是一个圆,这将非常有用。

void Entity::draw(Graphics g) {
     g.drawRect(x,y, width, height);
} 

然后,您将检查g是否在测试中调用了正确的方法。


答案 2

你说你觉得你想出的渲染器类是由TDD“特别强迫”的。那么,让我们来看看TDD将您带到了哪里。从负责其坐标和绘制自身的 Rectangle 类,到具有维护其坐标的单一职责的 Rectangle 类,以及具有呈现矩形的单一职责的渲染器。这就是我们所说的测试驱动的意思 - 这种做法会影响你的设计。在这种情况下,它驱使你设计一个更紧密地遵循单一责任原则 - 一个没有测试你就不会去的设计。我认为这是一件好事。我认为你练习TDD很好,我认为它对你有用。


推荐