2010-09-27 4 views
15

Я изучаю развитие, основанное на тестах, и я заметил, что он заставляет свободно связанные объекты, что в принципе хорошо. Однако это также иногда заставляет меня предоставлять аксессуры для свойств, которые мне не нужны в обычном режиме, и я думаю, что большинство людей на 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 ключевое слово «friend» из C++?

Update:

Спасибо за ваши ценные входы до сих пор! Однако, боюсь, я выбрал плохой пример для иллюстрации моей проблемы. Это было полностью сделано, я теперь продемонстрировать тот, который ближе к моему реальному коду:

@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 в будущем.

Update 2:

Думая об этом еще немного, может быть, это хорошо, как это. Возможно, это нормально, что объект и чертеж отделены друг от друга и что объект сообщает другим объектам достаточно о себе для рисования. Я имею в виду, как еще я мог бы достичь этого разделения? И я не вижу, как жить без него. Даже великие объектно-ориентированные программисты иногда используют объекты с геттерами/сеттерами, особенно для чего-то вроде объекта сущности. Может быть, геттер/сеттер не все злы. Как вы думаете?

+1

Посмотрите на это так: если 'Entity' не будет' getY', вам нужно его протестировать? Теоретически, все, что недоступно для другого кода, является внутренним по отношению к 'Entity', а не к значению для пользователя, а также расширением теста. –

+0

Но я мог бы добавить, что игры (которые, похоже, это так) являются особенно сложными для модульного теста. –

+0

«Дружеское ключевое слово» java является модификатором доступа по умолчанию, каждый класс в одном пакете может обращаться к полям и методам, которые не имеют модификатора доступа (без публичного, частного или защищенного). Тесты могут получить доступ к этим полям, если они используют одну и ту же структуру пакета, поэтому вы можете сохранить эти данные в пакете. – josefx

ответ

3

Вы говорите, что чувствуете, что класс Renderer, с которым вы столкнулись, был «специально вынужден» TDD. Итак, давайте посмотрим, куда ведет TDD. Из класса Rectangle, который отвечал за его координаты и для рисования, относится к классу Rectangle, который имеет единую ответственность за сохранение своих координат и рендереру, который имеет единую ответственность, а также рендеринг Rectangle. Это то, что мы имеем в виду, когда говорим Test Driven - эта практика влияет на ваш дизайн. В этом случае это привело вас к дизайну, который более тесно связан с Single Responsibility Principle - дизайном, с которым вы бы не пошли, без тестов. Я думаю, что это хорошо. Я думаю, вы хорошо тренируетесь с TDD, и я думаю, что он работает на вас.

5

Прагматические программисты обсуждают tell, don't ask. Вы не хотите знать о сущности, вы хотите, чтобы ее рисовали. Скажите, чтобы он рисовал себя на данной графике.

Вы можете реорганизовать код выше, чтобы объект рисовал, что полезно, если объект не является прямоугольником, а фактически кругом.

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

После этого вы проверили бы, что в ваших тестах были вызваны правильные методы.

+0

Как объяснялось выше, у меня был метод рисования сущностью, но я намеренно убрал его, чтобы избежать жесткой связи с графической системой. – Noarth

+0

Я думаю, что это проблема с абстракцией графики, а не с абстракцией Entity. Как и в MVC, вам нужен слой абстракции между тем, как (конкретный) и тем (абстрактным) вашей графикой. –

0

Что вы хотите проверить, так это то, как ваш объект будет отвечать на определенные вызовы, а не как он работает внутри.

Поэтому не нужно (и было бы плохой идеей) получить доступ к доступным полям/методам.

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

2

Итак, если бы вы не переместили метод draw(Graphics) из Entity, у вас был вполне проверяемый код. Вам нужно только ввести реализацию Graphics, в котором сообщается внутреннее состояние Entity на ваш тестовый жгут. Просто мое мнение.

1

Прежде всего, знаете ли вы о классе java.awt.Rectangle, как именно эта проблема рассматривается в Java Runtime Library?

Во-вторых, я считаю, что реальные значения TDD - это прежде всего то, что он перемещает ваш фокус от «как мне сделать эту конкретную деталь с данными, которые, как я полагаю, присутствует как это», «как мне вызвать код и какого результата я ожидаю ». Традиционный подход - «исправить детали, а затем мы выясним, как вызывать код», а сделать это наоборот - позволяет быстрее найти, если что-то просто не может быть сделано или нет.

Это очень важно при разработке API, что, скорее всего, также объясняет, почему вы обнаружили, что ваш результат слабо связан.

Второе значение заключается в том, что ваши тесты являются «живыми комментариями», а не древними, нетронутыми комментариями. Тест показывает, как должен вызываться ваш код, и вы можете сразу убедиться, что он ведет себя как указано. Это не имеет отношения к тому, что вы просите, но должно продемонстрировать, что тесты имеют больше целей, а затем просто слепо называть код, который вы написали некоторое время назад.

0

Я думаю, что ваш пример имеет много общего с примером используется здесь:

http://www.m3p.co.uk/blog/2009/03/08/mock-roles-not-objects-live-and-in-person/

Используя свой оригинальный пример и заменяющий Субъекта с Героем, падение() с jumpFrom (балкон), и рисовать() как moveTo (Room), он становится удивительно похожим. Если вы используете подход макет-объекта, предложенный Стивом Фрименом, ваша первая реализация была не так уж плоха в конце концов. Я считаю, что @Colin Хеберт дал лучший ответ, когда указал в этом направлении. Здесь нет никакой необходимости раскрывать что-либо.Вы используете фиктивные объекты, чтобы проверить, произошло ли поведение героя.

Обратите внимание, что автор статьи имеет в соавторство большой книги, которая может помочь вам:

http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627

Есть некоторые хорошие документы свободно доступны в формате PDF авторов об использовании фиктивных объектов для руководства ваш дизайн в TDD.

+0

Я не думаю, что я могу использовать макет объекта для рисования, потому что я буду использовать средство визуализации для GWT. Я не могу создать графический объект для GWT, это не сработает, потому что в GWT нет рисунка, есть просто модификация элементов DOM, и я не хочу, чтобы элементы DOM в моем основном игровом логическом пакете. – Noarth

+0

Кроме того, я не думаю, что метод draw такой же, как метод moveTo. Мой метод рисования - это низкоуровневый графический чертеж, а moveTo - логическая операция, которую я бы тестировал. – Noarth