smack0007
smack0007

Reputation: 11356

Xna: Mocking Texture2D

I'm writing WinForms / Xna app and I need some way to abstract away interaction with the GraphicsDevice in my Controller / Model code.

I've created an interface IGraphicsService. I'll use that to abstract away things like loading textures. What I can't figure out though is what to do when I need to return some kind of Texture information. Should I create a class that wraps Texture2D? I'm worried this will incur unnecessary overhead. I would like to be able to create some kind of MockTexture2D in the long run.

This is all so that I can make my app more testable. I'm not so much worried about speed but it would be nice if there was some solution that won't incur to much overhead as eventually I want to use this to make my games more testable. Any suggestions?

Upvotes: 4

Views: 1550

Answers (4)

Florin Mircea
Florin Mircea

Reputation: 973

I know this might go a bit outside the usecase, but maybe you can get rid of the need for a texture. I don't believe it is needed to test the texture itself, rather the data it contains.

So, for instance, if you have this method doing something with your texture:

string output = ConvertTextureToSomeTextualInfo(yourTexture);

...

public static string ConvertTextureToSomeTextualInfo(Texture2D texture) {
    // your magic
}

Instead of sending the texture, send the data:

Color[] texData = new Color[yourTexture.width * yourTexture.height];
yourTexture.GetData(texData);

string output = ConvertTextureToSomeTextualInfo(texData);

public static string ConvertTextureToSomeTextualInfo(Color[] textureData) {
    // still, your magic
}

This may not be that easy in the given case, but I am pretty certain the code can be abstracted a bit more to separate the logic from the UI stuff as much as possible and only let a small component connect those together while confortably testing the logic itself.

Upvotes: 0

Alexander Brandbyge
Alexander Brandbyge

Reputation: 1

I hit this problem as well when trying to use dummy textures in a unit test. This is how i got around it:

internal class SneakyTexture2D : Texture2D
{
    private static readonly object Lockobj = new object();
    private static volatile Texture2D instance;

    private SneakyTexture2D()
        : this(() => throw new Exception())
    {
    }

    private SneakyTexture2D(Func<GraphicsDevice> func)
        : this(func())
    {
    }

    // Is never called
    private SneakyTexture2D(GraphicsDevice g)
        : base(g, 0, 0)
    {
    }

    // INTENTIONAL MEMORY LEAK AHOY!!!
    ~SneakyTexture2D()
    {
        instance = this;
    }


    // This is the actual "constructor"
    public static Texture2D CreateNamed(string name)
    {
        lock (Lockobj)
        {
            Texture2D local;
            instance = null;
            while ((local = instance) == null)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();

                GC.WaitForFullGCComplete();
                try
                {
                    DoNotUseMe = new SneakyTexture2D();
                }
                catch
                {
                }
            }

            local.Name = name;
            return local;
        }
    }
}

The SneakyTexture2D Class, while extending from Texture2D, gets away with not calling/initializing the base object constructor and will therefore not require a Graphicsdevice.

The fact that i am using the Name property on the Texture is a bit of a gamble. In general interacting with an uninitialized object is a bit dangerous.

Huge shout-out to Jon Skeets brilliant article on the subject:https://codeblog.jonskeet.uk/2014/10/23/violating-the-smart-enum-pattern-in-c/

Upvotes: 0

Cygon
Cygon

Reputation: 9620

My personal opinion is that the GraphicsDevice class is too complex to be mocked and whoever did it despite this would have even more work to actually instruct the mock to do the right thing in his tests (though he should certainly get a medal for the effort :D).

Visual Studio's "Extract Interface" refactoring would only get you half the way there. Maybe a code generator could create a fully forwarding mock. If you worry about performance, as long as your mock interface mirrors the real graphics device, you could do some magic #if..#endif to use the real GraphicsDevice in a Release build and go through the interface in a Debug build?

-

Anyway, for my own unit tests, I'm actually creating a real graphics device on an invisible form for my unit tests. This works surprisingly well, the build agent running my unit tests runs on Windows XP in a VMware virtual machine, with Gentoo Linux x64 as the host OS. I'm testing actual rendering code with shaders and rendertargets and whatnot. Performance-wise, I also can't complain - 1300 tests execute in under 10 seconds.

This is the code I use to create the pseudo-mocked graphics device service: MockedGraphicsDeviceService.cs and its unit test: MockedGraphicsDeviceService.Test.cs

The drawback, of course, is that you cannot check for any expectations in such a pseudo-mocked graphics device (eg. did a call to CreateTexture() occur with a width of 234 and a height 456?). Takes some clever class design to isolate the logic enough so I can test my graphics classes without breaking up their abstraction. At least I can get test coverage on graphics code at all this way :)

Upvotes: 3

Joel Martinez
Joel Martinez

Reputation: 47809

You could try to use Scurvy.Test to write your unit tests. That way, you don't need to mock up the graphics device at all, just use the actual instance :-)

http://scurvytest.codeplex.com

disclaimer: I'm the author of that lib

Upvotes: 1

Related Questions