Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid accessors in test-driven development?

Tags:

java

tdd

I'm learning test-driven development, and I have noticed that it forces loosely coupled objects, which is basically a good thing. However, this also sometimes forces me to provide accessors for properties I wouldn't need normally, and I think most people on SO agree that accessors are usually a sign of bad design. Is this inevitable when doing TDD?

Here is an example, the simplified drawing code of an entity without 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);
    }
}

The entity knows how to draw itself, that's good. All in one place. However, I'm doing TDD, so I want to check whether my entity was moved correctly by the "fall()" method I am about to implement. Here is what the test case could look like:

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

I have to look at the object's internal (well, at least logically) state and see if the position was updated correctly. Since it's actually in the way (I don't want my test cases to depend on my graphics library), I moved the drawing code to a class "Renderer":

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

Loosely coupled, good. I can even replace the renderer with one that displays the entity in a completely different way. However, I had to expose the internal state of the entity, namely the accessors for all of its properties, so that the renderer could read it.

I feel that this was specifically forced by TDD. What can I do about this? Is my design acceptable? Does Java need the "friend" keyword from C++?

Update:

Thanks for your valuable inputs so far! However, I fear I have chosen a bad example for illustrating my issue. This was completely made up, I will now demonstrate one that is closer to my actual code:

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

This is a game loop based implementation of a game where an entity can fall down, among other things. If it hits the ground, a new entity is created.

The "mockRenderer" is a mock implementation of an interface "Renderer". This design was partly forced by TDD, but also because of the fact that I'm going to write the user interface in GWT, and there is no explicit drawing in the browser (yet), so I don't think it is possible for the Entity class to assume that responsibility. Furthermore, I would like to keep the possibility of porting the game to native Java/Swing in the future.

Update 2:

Thinking about this some more, maybe it's okay how it is. Maybe it's okay that the entity and the drawing is separated and that the entity tells other objects enough about itself to be drawn. I mean, how else could I achieve this separation? And I don't really see how to live without it. Even great object-oriented programmers use objects with getters/setters sometimes, especially for something like an entity object. Maybe getter/setter are not all evil. What do you think?

like image 295
Noarth Avatar asked Sep 27 '10 21:09

Noarth


People also ask

What are the 3 steps of test driven development?

“Test-driven development” refers to a style of programming in which three activities are tightly interwoven: coding, testing (in the form of writing unit tests) and design (in the form of refactoring).

Is TDD good for agile?

Test driven development is a core Agile practice. It directly supports the Agile value of “Working software over comprehensive documentation”. And does so by protecting working software with tests and creating the documentation as a natural by-product.


1 Answers

The Pragmatic Programmers discuss tell, don't ask. You don't want to know about the entity, you want it to be drawn. Tell it to draw itself on the given Graphics.

You could refactor the code above so that the entity does the drawing, which is useful if the entity isn't a rectangle but actually a circle.

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

You'd then check that g had the correct methods called on it in your tests.

like image 63
Paul Rubel Avatar answered Oct 05 '22 10:10

Paul Rubel