سؤال

كنت أتجول إذا كان من الممكن الاستهزاء بكائن اللعبة من أجل اختبار مكون DrawableGameComponent الخاص بي؟

أعلم أن الأطر الساخرة تحتاج إلى واجهة لكي تعمل، لكني بحاجة إلى الاستهزاء بالواقع لعبة هدف.

يحرر:هنا أ وصلة للمناقشة المعنية في منتديات مجتمع XNA.أي مساعدة؟

هل كانت مفيدة؟

المحلول

هناك بعض المشاركات الجيدة في هذا المنتدى حول موضوع اختبار الوحدة.إليك طريقتي الشخصية في اختبار الوحدة في XNA:

  • تجاهل الأسلوب Draw()
  • عزل السلوك المعقد في أساليب الفصل الخاصة بك
  • اختبر الأشياء الصعبة، ولا تتعب في الباقي

فيما يلي مثال لاختبار للتأكد من أن طريقة التحديث الخاصة بي تنقل الكيانات إلى المسافة الصحيحة بين استدعاءات Update().(أنا استخدم وحدة.) لقد قمت بقص سطرين باستخدام متجهات حركة مختلفة، لكنك حصلت على الفكرة:لا ينبغي أن تحتاج إلى لعبة لإجراء اختباراتك.

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

يحرر:بعض الملاحظات الأخرى من التعليقات:

فئة الكيان الخاص بي:لقد اخترت تغليف جميع كائنات لعبتي في فئة كيان مركزية، والتي تبدو كما يلي:

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

يمنحني هذا الكثير من المرونة لإنشاء فئات فرعية للكيان (AIEntity، NonInteractiveEntity...) والتي ربما تتجاوز Update().كما أنه يسمح لي بفئة فرعية قابلة للرسم بحرية، دون الحاجة إلى فئات فرعية من نوع n^2 AnimatedSpriteAIEntity, ParticleEffectNonInteractiveEntity و AnimatedSpriteNoninteractiveEntity.بدلاً من ذلك، يمكنني القيام بذلك:

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

صفي القابل للرسم:لدي فئة مجردة يتم اشتقاق جميع الكائنات المرسومة منها.لقد اخترت فصلًا مجردًا لأنه سيتم مشاركة بعض السلوكيات.سيكون من المقبول تمامًا تعريف هذا على أنه واجهه المستخدم بدلاً من ذلك، إذا لم يكن هذا صحيحًا بالنسبة للتعليمات البرمجية الخاصة بك.

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

تحدد الفئات الفرعية منطق الرسم الخاص بها.في مثال الخزان الخاص بك، يمكنك القيام ببعض الأشياء:

  • أضف كيانًا جديدًا لكل رمز نقطي
  • أنشئ فئة TankEntity التي تحدد القائمة وتتجاوز Draw() للتكرار على التعداد النقطي (الذي يحدد طريقة رسم خاصة بها)
  • إنشاء قائمة قابلة للرسم

فيما يلي مثال لتطبيق ListDrawable، متجاهلاً مسألة كيفية إدارة القائمة نفسها.

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

نصائح أخرى

والأطر مثل موك و <لأ href = "http://ayende.com /projects/rhino-mocks.aspx "يختلط =" نوفولو noreferrer "> الكركدن يسخر لا تحتاج على وجه التحديد واجهة. ويمكن أن يسخر أي فئة غير مختومة و / أو مجردة كذلك. اللعبة هي فئة مجردة، لذلك يجب أن لا تكون هناك أي مشكلة ساخرا منه: -)

وشيء واحد أن نلاحظ مع تلك اثنين على الأقل الأطر هو أن وضع أي توقعات عن أساليب أو خصائص، يجب أن تكون الظاهري أو مجردة. والسبب في ذلك هو أن المثال سخر أنه يولد يجب أن يكون قادرا على تجاوز. وtypemock التي كتبها IAmCodeMonkey المذكورة أعتقد لديه وسيلة للتغلب على هذه، لكنني لا أعتقد typemock غير مجانا، في حين أن اثنين ذكرتها هي.

وبوصفها جانبا، يمكنك أيضا الاطلاع على المشروع من الألغام التي يمكن أن تساعد في خلق وحدة الاختبارات للألعاب XNA دون الحاجة إلى جعل يسخر: <لأ href = "http://scurvytest.codeplex.com/" يختلط = "نوفولو noreferrer"> http://scurvytest.codeplex.com/

وليس لديك للسخرية منه. لماذا لا تجعل كائن لعبة وهمية؟

وراثة من لعبة وتجاوز الأساليب التي تنوي استخدامها في الاختبارات الخاصة بك لإرجاع القيم المعلبة أو حسابات الاختصار لأي الأساليب / الخصائص التي تحتاج إليها. ثم تمرير همية لاختبارات الخاص بك.

وقبل الأطر ساخرا، وتوالت شعبهم يسخر / بذرة / مزيفة - ربما انها ليست سريعة وسهلة، ولكن كنت لا تزال

.

ويمكنك استخدام أداة تسمى TypeMock التي أعتقد أنه لا يتطلب منك أن يكون واجهة. من طريقة أخرى ويتبع أكثر شيوعا هو خلق فئة جديدة يرث من لعبة، وكذلك تنفذ واجهة التي تقوم بإنشائها يطابق الكائن لعبة. يمكنك بعد ذلك كود ضد تلك الواجهة وتمر في حياتك "مخصص" وجوه لعبة.

public class MyGameObject : Game, IGame
{
    //you can leave this empty since you are inheriting from Game.    
}

public IGame
{
    public GameComponentCollection Components { get; set; }
    public ContentManager Content { get; set; }
    //etc...
}

ولها مملة بعض الشيء، ولكنه يتيح لك تحقيق mockability.

وأنا ستعمل أصبع مرة أخرى على مشاركتك إذا كنت لا تمانع منذ <لأ href = "https://stackoverflow.com/questions/799869/unit-testing-xna-do-i-need-to- التصميم الجرافيكي، بلدي graphicsdevice "> الألغام يبدو أن قلة الحركة وكنت وضعت بالفعل ممثل الخاص بك على الخط؛)

وكما قرأت من خلال مشاركاتك (سواء هنا وXNAForum) أنا أفكر أنه على حد سواء الإطار الذي يمكن أن يكون أكثر ودود وبلدي (لدينا) تصميم هذا لا تشوبه شائبة.

ولقد يكون قد صمم الإطار ليكون أسهل للالموسعة. أواجه صعوبة في الاعتقاد شون الصورة الحجة الرئيسية ل PERF ضرب على واجهات . لدعم لي زميله يقول ضرب الأداء الإقتصادي الأداء يمكن أن يكون تهرب easely.
هل لاحظ أن الإطار بالفعل واجهة IUpdatable وIDrawable. لماذا لا تذهب على طول الطريق؟

من ناحية أخرى أعتقد أيضا أن الواقع بلدي (والخاص) تصميم لا تشوبه شائبة. حيث لا تعتمد على وجوه لعبة، وأنا لا تعتمد على GraphicsDevice يعترض الكثير. أنا ستعمل نظرة على كيف يمكن الالتفاف على هذا. انها ستعمل جعل رمز أكثر تعقيدا، ولكن أعتقد أنني أستطيع كسر في الواقع تلك التبعيات.

لنقطة انطلاق على شيء مثل هذا، وأود أن ضرب حتى XNA WinForms عناصر عينة . باستخدام هذا النموذج كنموذج، يبدو أن طريقة واحدة لتصور مكونات في WinForm هي لإنشاء عنصر تحكم لذلك في نفس النمط كما SpinningTriangleControl من العينة. هذا يوضح كيفية تقديم كود XNA دون مثيل لعبة. حقا لعبة ليست مهمة، ما لها هل لك أن الأمور. ذلك ما يمكن ان تفعله هو إنشاء مشروع مكتبة التي لديها تحميل / رسم منطق مكون في فئة وفي المشاريع الأخرى الخاصة بك، وخلق طبقة تحكم وطبقة المكونات التي هي مغلفة لرمز مكتبة في بيئات كل منهما. بهذه الطريقة، رمز لا تكرار الاختبار الخاص بك، وكنت لا داعي للقلق حول كتابة التعليمات البرمجية التي سوف تكون دائما قابلة للحياة في اثنين من سيناريوهات مختلفة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top