2011年2月10日 星期四

Acceptance Tests

Extreme programming uses testing two ways: Customers develop acceptance tests that determine the overall behavior of the system, and Programmers create unit tests while programming.

As a Customer, you'll create tests for each story you request. You'll measure the progress in developing the system by running these tests.

目標: 偷竊物理模擬小遊戲.

物理模擬部分,已經抽出「合適」的 interfaces,implementor 採用 Box2D,為了「證明」Box2D 滿足遊戲 (customer) 需求,自然要有幾個簡單的小測試。這種測試就稱為 acceptance tests。

第一個測試就是自由落體 (freefall):
TEST(Box2d, Freefall) {
Service* service = new Box2dService();

// Construct a world, which will hold and simulate the rigid bodies.
WorldConfig world_config;
world_config.gravity.set(0.0f, -10.0f);
World* world = service->createWorld(world_config);

// Config ground body.
BodyConfig ground_config;
ground_config.position.set(0.0f, -10.0f);
Body* ground = world->createBody(ground_config);

// Config ground body fixture.
PolygonShapeConfig ground_shape_config;
ground_shape_config.setAsBox(50.0f, 10.0f);

FixtureConfig ground_fixture_config;
ground_fixture_config.shape_config = &ground_shape_config;
ground_fixture_config.density = 0.0f;
ground->createFixture(ground_fixture_config);

// Config dynamic body.
BodyConfig dynamic_config;
dynamic_config.type = BodyConfig::Dynamic;
dynamic_config.position.set(0.0f, 4.0f);
Body* dynamic = world->createBody(dynamic_config);

// Config dynamic body fixture.
PolygonShapeConfig dynamic_shape_config;
dynamic_shape_config.setAsBox(1.0f, 1.0f);

FixtureConfig dynamic_fixture_config;
dynamic_fixture_config.shape_config = &dynamic_shape_config;
dynamic_fixture_config.density = 1.0f;
dynamic_fixture_config.friction = 0.3f;
dynamic->createFixture(dynamic_fixture_config);

// Simulate.
const float time_step = 1.0f/60.0f;
const int steps = 60;
const float expected[] = {
3.997222f, 3.991667f, 3.983333f, 3.972222f, 3.958333f, 3.941667f,
3.922222f, 3.900000f, 3.875000f, 3.847222f, 3.816667f, 3.783333f,
3.747222f, 3.708333f, 3.666667f, 3.622222f, 3.575000f, 3.525000f,
3.472222f, 3.416667f, 3.358333f, 3.297222f, 3.233333f, 3.166667f,
3.097222f, 3.025000f, 2.950000f, 2.872222f, 2.791667f, 2.708333f,
2.622222f, 2.533333f, 2.441667f, 2.347222f, 2.250000f, 2.150000f,
2.047222f, 1.941667f, 1.833333f, 1.722222f, 1.608334f, 1.491667f,
1.372223f, 1.250000f, 1.125000f, 1.014582f, 1.014651f, 1.014708f,
1.014756f, 1.014796f, 1.014830f, 1.014858f, 1.014881f, 1.014900f,
1.014917f, 1.014930f, 1.014942f, 1.014951f, 1.014959f, 1.014966f
};
EXPECT_EQ(steps, arraysize(expected));

for (int i = 0; i < steps; ++i) {
world->simulate(time_step);
EXPECT_TRUE(fabs(dynamic->position().x()) < 0.001f);
EXPECT_FLOAT_EQ(expected[i], dynamic->position().y());
EXPECT_TRUE(fabs(dynamic->angle()) < 0.001f);
}

// Clean up.
delete world;
delete service;
}
程式碼有點長,偷自 Box2d's HelloWorld,哈哈,天下文章一般抄。若要比較 float/double 大小,EXPECT_FLOAT_EQ() macro 是一個很好的選擇。總之,此 acceptance test 沒過,我們必須讓他過。(只貼有意思的部分)
// box2d_world.cc
Body* Box2dWorld::createBody(const BodyConfig& config) {
b2BodyDef config_impl;

config_impl.position.Set(config.position.x(), config.position.y());
config_impl.angle = config.angle;
config_impl.linearVelocity.Set(config.linear_velocity.x(),
config.linear_velocity.y());
config_impl.angularVelocity = config.angular_velocity;
config_impl.linearDamping = config.linear_damping;
config_impl.angularDamping = config.angular_damping;
if (config.type == BodyConfig::Static) {
config_impl.type = b2_staticBody;
} else if (config.type == BodyConfig::Dynamic) {
config_impl.type = b2_dynamicBody;
} else if (config.type == BodyConfig::Kinematic) {
config_impl.type = b2_kinematicBody;
}
config_impl.active = config.is_active;

return new Box2dBody(itself_->CreateBody(&config_impl));
}

// box2d_body.cc
void Box2dBody::createFixture(const FixtureConfig& config) {
if (config.shape_config->type == ShapeConfig::Polygon) {
PolygonShapeConfig* shape
= dynamic_cast< PolygonShapeConfig* >(config.shape_config);

b2PolygonShape shape_impl;
shape_impl.m_vertexCount = static_cast(shape->vertices.size());
for (int i = 0; i != shape_impl.m_vertexCount; ++i) {
shape_impl.m_vertices[i].Set(shape->vertices[i].x(),
shape->vertices[i].y());
shape_impl.m_normals[i].Set(shape->normals[i].x(),
shape->normals[i].y());
}
shape_impl.m_centroid.Set(shape->centroid.x(), shape->centroid.y());

b2FixtureDef config_impl;
config_impl.shape = &shape_impl;
config_impl.friction = config.friction;
config_impl.restitution = config.restitution;
config_impl.density = config.density;

itself_->CreateFixture(&config_impl);
}
}

偷加一個 method 給 PolygonShapeConfig:
void PolygonShapeConfig::setAsBox(float half_x, float half_y) {
vertices.push_back(math::Vector2(-half_x, -half_y));
vertices.push_back(math::Vector2( half_x, -half_y));
vertices.push_back(math::Vector2( half_x, half_y));
vertices.push_back(math::Vector2(-half_x, half_y));

normals.push_back(math::Vector2( 0.0f, -1.0f));
normals.push_back(math::Vector2( 1.0f, 0.0f));
normals.push_back(math::Vector2( 0.0f, 1.0f));
normals.push_back(math::Vector2(-1.0f, 0.0f));

centroid.setZero();
}
跑一跑居然過了,太神奇了傑克。這就是一個簡單的 acceptance test,謝謝大家收看。

沒有留言:

張貼留言