пятница, 23 декабря 2011 г.

Система юнит тестов - логический компилятор вашего приложения

"A unit test is almost always written using a unit-testing framework" Roy Osherove, The Art of Unit Testing.
Всем привет.

Сегодня хотел поговорить о юнит тестах. Я не буду спорить о необходимости тестов в вашем проекте. Данный момент каждый определяет для себя. Кто-то считает, что это отстой, кто-то не может без них писать код, кто-то считает что именно на его проекте и в его команде это не нужно.
Сам я на данный момент нахожусь в стадии: "юнит тесты это круто, нужно и полезно, но сам я их никогда не использовал в реальном проекте и писать не умею".

Тему эту я решил поднять поскольку был на тренинге "TDD в .NET". Тренинг прошел круто, полезно и об этом можно почитать уже в нескольких отчетах. Просто спросите гугл: TDD в .NET на XPDays и он вам все расскажет.

Но что я вынес для себя и почему выбрал именно такой заголовок статьи, а не другой?
За свои скромные несколько лет работы я вынес одну банальную вещь - программисты народ ленивый, менеджерам всегда не хватает времени, поэтому хватит экспериментов, делайте как работает и не ерзайте. Соответственно, если вы хотите ввести какую-то полезную практику у себя на проекте, то в первую очередь позабодтесь об удобных инструментах для работы. Будь-то ревью кода, continuos integration, continous delivery.

Так вот возвращаясь к теме TDD. До тренинга я думал, что писать тесты до кода это сложно, непонятно, скучно. Но как оказалось наука ушла далеко вперед. Наши тренеры (Александр Белецкий @alexbeletsky и Сергей Калинетц @skalinets) показали нам связку из плагинов для студии и фреймворков для юнит тестов, которые делают разработку тестов удобной, быстрой, а их проверку и запуск настолько нативной, насколько для меня уже стала перекомпиляция проекта. Вам не нужно даже сохранять файлы с вашими изменениями в тестах и логике, для того чтобы видеть ошибки и проваленные тесты.
Потом, вечерком переваривая всю полученную информацию я и подумал о наборе юнит тестов, как об еще одном уровне компилятора - компиляторе вашей логики, который работает в фоновом режиме и сразу же проверяет насколько были правильными ваши изменения.

Инструменты следующие, очень советую посмотреть всем кто пишет на C#:
  • xUnit (NUnit) - фреймворки для юнит тестов
  • NSubstitude - фреймворм для создания моков(заглушек, пустышек для тестов)
  • Test Driven.NET - плагин, который позволяет запускать тесты под разными тулзами, и под дебагом в том числе
  • NCrunch - плагин, который в фоне запускает тесты и выдает вам красивый результат в картинках, визуально показывает покрыт ли ваш код тестами
Конечно следующим шагом стоит поднять все темы касательно написания сложных тестов, сложностей их реального применения и самое главное поддержки актуальности ваших тестов. Я этого делать не буду поскольку не имею такого опыта и могу только повторять чужие слова. Но сейчас мой проект использует .NET технологии, я обязательно попробую TDD и мы сможем похоливорить на эти темы в следующих постах.

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

Всем доброй ночи. 

вторник, 6 сентября 2011 г.

Simple Render. Интерфейс IRenderer

Продолжаем рассматривать демо с организацией простенького рендера. В прошлой заметке я рассмотрел интерфейс IEntity и его реализацию. Сегодня у нас очередь интерфейса IRenderer, который в моей схеме представляет view.
Интерфейс IRenderer достаточно прост и содержит всего один важный для нас метод render, метод в котором и будет находиться код по отрисовке той или иной сущности.
class IRenderer
{
public:
virtual ~IRenderer()
{
}
virtual void render()=0;
};
Посмотрим теперь простенькую реализацию данного интерфейса, для отрисовки объектов типа GameEntity, о которых я говорил в прошлой заметке.

class SimpleRenderer2D : public IRenderer
{
private:
GameEntity& m_Entity;
protected:
const GameEntity& getGameEntity()
{
return m_Entity;
}
public:
SimpleRenderer2D(GameEntity& entity);
virtual void render();
};
И соответственно дальше реализация метода render в данном классе:

void SimpleRenderer2D::render()
{
glPushMatrix();
glTranslatef(getGameEntity().getPosition().x, getGameEntity().getPosition().y, 0.0f);
glColor3ub(255,0,0);
glBegin(GL_QUADS);
glVertex2f(0,25);
glVertex2f(0,0);
glVertex2f(25,0);
glVertex2f(25,25);
glEnd();
glPopMatrix();
}
Теперь все выглядит, как мне кажется, просто и наглядно. Класс GameEntity не имеет никакого представления о классе SimpleRenderer2D, а значит мы можем добавлять и другие view для данного типа объектов, использовать их по своему усмотрению и это никак не отражается на нашей модели.

Остается только собрать все эти классы вместе и запустить наше приложение. Этим мы и займемся в следующей заметке.
Всем доброй ночи. 

вторник, 30 августа 2011 г.

Simple render. Интерфейс IEntity

Всем привет 

Сегодня после небольшого перерыва я продолжу рассказ о своем примере отрисовки.
На сегодня у нас один небольшой вопрос - это создание интерфейса IEntity, а также его реализация в наследнике
Итак поехали 

Интерфейс IEntity представляет любую игровую сущность, это базовый класс для любого игрового объекта. Он на самом деле не большой. Как мне кажется на таком уровне нужно только две вещи:
- сохранение и возможность получения уникального идентификатора нашего объекта
- возможность обновления объекта, выполнения в нем какой-то логики

Соответственно интерфейс имеет следующую реализацию 
       class IEntity
       {
              static unsigned long m_EntityIDCounter;
              int m_EntityID;

       public:
              inline int getEntityID()const
              {
                     return m_EntityID;
              }
              
       private:
              inline void setEntityID(int id)
              {
                     m_EntityID = id;
              }

       public:
              IEntity()
              {
                     setEntityID(++m_EntityIDCounter);
              }

              virtual ~IEntity()
              {
              }

              virtual void update() = 0;
       private:
       };
Целочисленное поле m_EntityID содержит уникальный идентификатор объекта, получить который можно через метод getEntityID, а устанавливается он автоматически при создании объекта. Для того чтобы обеспечить уникальной каждого идентификатора я использую статическую переменную m_EntityIDCounter. Думаю промежутка значений, который можно хранить в long нам хватит выше крыши для идентификаторов. 

Ну а для обновления объекта создан абстрактный метод update. На данный момент он не принимает никаких параметров. 

Идем дальше. В примере я создал только один класс наследник - это класс, который представляет двухмерный объект, как статический, так и динамический. Этот объект не содержит логики, но мы потом сможем это добавить, сейчас же это не важно. 
Вот код нового класса:
       class GameEntity : public IEntity
       {
       private:

              glm::vec2 m_Position;
       public:
              inline const glm::vec2& getPosition()const
              {
                     return m_Position;
              }

              inline void setPosition(const glm::vec2& pos)
              {
                     m_Position = pos;
              }

       public:
              GameEntity();

              virtual void update();
       };

Конструктор просто инициализирует позицию нулями 
       GameEntity::GameEntity()
       {
              m_Position.x = m_Position.y = 0.0f;
       }
А метод update вообще пока не содержит логики
       void GameEntity::update()
       {
       }

Собственно все. 
Теперь у нас есть модель, которую нужно визуализировать. Осталось только дописать вид и все будет в шоколаде. 

Всем доброй ночи.