Có một số bài đăng hay trong diễn đàn đó về chủ đề kiểm tra đơn vị. Dưới đây là cách tiếp cận cá nhân của tôi để kiểm tra đơn vị trong XNA:
- Bỏ qua Draw() phương pháp
- Cô lập hành vi phức tạp trong phương thức lớp riêng bạn
- thử nghiệm những thứ phức tạp, không đổ mồ hôi phần còn lại
Dưới đây là ví dụ về thử nghiệm để xác nhận rằng phương thức Cập nhật của tôi di chuyển Thực thể ở khoảng cách phù hợp giữa các cuộc gọi Update(). (Tôi đang sử dụng NUnit.) Tôi đã cắt bớt một vài dòng với các vector chuyển động khác nhau, nhưng bạn có ý tưởng: bạn không cần một Trò chơi để lái thử nghiệm của mình.
[TestFixture]
public class EntityTest {
[Test]
public void testMovement() {
float speed = 1.0f; // units per second
float updateDuration = 1.0f; // seconds
Vector2 moveVector = new Vector2(0f, 1f);
Vector2 originalPosition = new Vector2(8f, 12f);
Entity entity = new Entity("testGuy");
entity.NextStep = moveVector;
entity.Position = originalPosition;
entity.Speed = speed;
/*** Look ma, no Game! ***/
entity.Update(updateDuration);
Vector2 moveVectorDirection = moveVector;
moveVectorDirection.Normalize();
Vector2 expected = originalPosition +
(speed * updateDuration * moveVectorDirection);
float epsilon = 0.0001f; // using == on floats: bad idea
Assert.Less(Math.Abs(expected.X - entity.Position.X), epsilon);
Assert.Less(Math.Abs(expected.Y - entity.Position.Y), epsilon);
}
}
Edit: Một số lưu ý khác từ các ý kiến:
My Entity Lớp: tôi đã chọn để bọc tất cả trò chơi của tôi đối tượng lên trong một lớp Entity tập trung, mà trông giống như sau:
public class Entity {
public Vector2 Position { get; set; }
public Drawable Drawable { get; set; }
public void Update(double seconds) {
// Entity Update logic...
if (Drawable != null) {
Drawable.Update(seconds);
}
}
public void LoadContent(/* I forget the args */) {
// Entity LoadContent logic...
if (Drawable != null) {
Drawable.LoadContent(seconds);
}
}
}
Điều này mang lại cho tôi rất nhiều sự linh hoạt để làm cho các lớp con của thực thể (AIEntity, NonInteractiveEntity ...) có thể ghi đè lên Update(). Nó cũng cho phép tôi phân lớp Drawable một cách tự do, mà không có địa ngục của^2 lớp con như AnimatedSpriteAIEntity
, ParticleEffectNonInteractiveEntity
và AnimatedSpriteNoninteractiveEntity
. Thay vào đó, tôi có thể làm điều này:
Entity torch = new NonInteractiveEntity();
torch.Drawable = new AnimatedSpriteDrawable("Animations\litTorch");
SomeGameScreen.AddEntity(torch);
// let's say you can load an enemy AI script like this
Entity enemy = new AIEntity("AIScritps\hostile");
enemy.Drawable = new AnimatedSpriteDrawable("Animations\ogre");
SomeGameScreen.AddEntity(enemy);
lớp drawable My: Tôi có một lớp trừu tượng mà từ đó tất cả các đối tượng vẽ của tôi đều có nguồn gốc. Tôi đã chọn một lớp trừu tượng vì một số hành vi sẽ được chia sẻ. Thay vào đó, hoàn toàn có thể chấp nhận để xác định đây là một số interface, nếu điều đó không đúng với mã của bạn.
public abstract class Drawable {
// my game is 2d, so I use a Point to draw...
public Point Coordinates { get; set; }
// But I usually store my game state in a Vector2,
// so I need a convenient way to convert. If this
// were an interface, I'd have to write this code everywhere
public void SetPosition(Vector2 value) {
Coordinates = new Point((int)value.X, (int)value.Y);
}
// This is overridden by subclasses like AnimatedSprite and ParticleEffect
public abstract void Draw(SpriteBatch spriteBatch, Rectangle visibleArea);
}
Các lớp con xác định logic Vẽ của riêng chúng. Trong ví dụ xe tăng của bạn, bạn có thể làm một vài điều:
- Thêm một thực thể mới cho mỗi viên đạn
- Tạo một lớp TankEntity trong đó xác định một danh sách, và ghi đè Vẽ() để lặp qua các Bullets (trong đó xác định một phương thức Draw của riêng mình)
- thực hiện một ListDrawable
Dưới đây là một ví dụ về thực hiện ListDrawable, bỏ qua vấn đề làm thế nào để quản lý danh sách riêng của mình.
public class ListDrawable : Drawable {
private List<Drawable> Children;
// ...
public override void Draw(SpriteBatch spriteBatch, Rectangle visibleArea) {
if (Children == null) {
return;
}
foreach (Drawable child in children) {
child.Draw(spriteBatch, visibleArea);
}
}
}
Chúc may mắn. Trên SlimDX, chúng tôi đang đánh giá một chuyển đổi bán buôn qua các giao diện để xử lý trường hợp sử dụng này. – Promit
@Promit, Giao diện không phải là một viên đạn bạc ... trong một số trường hợp, một lớp trừu tượng là sự lựa chọn đúng đắn. Mặc dù giao diện chắc chắn hữu ích và chính xác trong các tình huống nhất định, nhưng nó không phải lúc nào cũng là công cụ thích hợp :-) –
@Jelel Martinez - khi nói đến chế giễu/giả mạo, khi nào giao diện không phải là công cụ phù hợp? –