Xna: Mocking Texture2D
Asked Answered
J

4

4

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?

Jejunum answered 20/8, 2009 at 20:17 Comment(0)
T
3

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

Tieratierce answered 21/8, 2009 at 19:56 Comment(3)
Your solution is actually interesting but it's a little more than I'm actually looking to test. I have a TileMap and a tile map needs a TileSheet. A TileSheet in turn needs a texture. I'm looking for some way to break that dependency. I think I've got it now though. I've created IGraphicsService and ITextureResource interfaces and which abstract away loading textures and texture information. I don't think having a simple wrapper class for textures is going to be that big of a performance hit although I have no data to back that up so I could be wrong.Jejunum
Great idea to create a real, bare-bones, graphics device within the unit test.Wimer
Badly the links are not available anymoreLoathing
D
1

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

Dysfunction answered 22/8, 2009 at 17:31 Comment(3)
might be an idea to put a disclaimer on this answer Joel :)Durra
a disclaimer for what, that I wrote that test framework? sure I guess if you think it's necessary ... I don't gain anything if someone uses it or not ;-)Dysfunction
Yeah I'm probably being pedantic but better to err on the side of caution... just trying to make SO a better place! :) stackoverflow.com/faq#promotion)Durra
M
0

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/

Mccutcheon answered 14/9, 2018 at 13:51 Comment(0)
A
0

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.

Assail answered 23/3, 2023 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.