zaphod2 == :ph34r: :wink:
The answer is (which may not be to your liking :(): you don't!
The level of testing actually describe, what and how you test in your software. Unit testing means you test small sections (units, e.g.: functions) of your code, whether they do what the requirements and documentation tell. Testing ultra-high level concepts/code/functionality with unit testing is actually "bad" in the sense, that the higher level the functionality is (building on top of a bunch of other code and having a huge number of required/dependent components) becomes increasingly difficult. Less net win with the tests especially measured in time required to test and automate vs quality assurance return. Plus the tests (talking about a shit-load of unit tests with high coverage) may interfere a lot with code changes, and admit it, gameplay code is tweaked and changed a lot and even major modifications can occur.
Of course these problems can be mitigated with correct software design (software can be architected in a way to be easily testable and be flexible regarding changes, e.g.: plugable and composition based design + mocking comes to mind regarding testing tools), but I would advise to use the correct testing level for the correct feature level.
"Real" answer:
- When it comes to low-level code (e.g.: boiler-plate stuff on which a lot of other stuff will depend on in a game/software):
Unit test the hell out of it, make sure close to every statement, decision/branch and execution path of these functions work the way it is expected to work.
But again, you should not be opening files, loading assets, reading "real" sockets, playing sounds, or creating a window and drawing to the screen. These are much "harder" to test, and by their very nature, check the functionalities and integration of multiple software components (e.g.: not a simple "standalone" function call and its result but a huge amount of calls, system calls, and a bunch of setup/preliminary state).
When you reach a higher level, e.g.: I'm loading some assets from the disk and use them to produce graphics on the screen, or I'm loading an xml/yaml/json file and parse a list of components/entities out of it and check whether the thing end up with the correct scene at runtime, or I'm starting my network thread, open my sockets, listen on X and Y ports (maybe even simulate incoming packets just to see what happens), that is a new level called "integration" (if you go even higher you reach functional and system) testing.
This can be exercised using code the same way as with unit test, but you simply will not be able to write as many tests, as preliminary preparation parts may become huge (e.g.: I open a window, create a rendering context, load a texture, draw it on the screen etc...) and the actual tested part depends on a bunch of other parts already established/working/running. Also these tests usually become heavy in the time it takes to execute them, since a lot of things may happen in one compared to a unit test (e.g.: running 10K unit tests takes a minute, running 10K of these higher level ones may take hours or more...).
What usually happens, that a testing framework (just a small tool or a small library) is written/used-off-the-shelf for exercising your software for doing automated "integration"/"functional" testing. Some ideas about how this can/may work:
- The framework/tool sets up a window + a rendering context, runs some code (THE TEST-CASE), takes a screen-shot of the window and compares it to and old one taken as the reference.
- The framework/tool sets up the network system, starts simulating a bunch of packets previously captured with a tool like wireshark, runs some code (THE TEST-CASE + the captured packets are part of it too) to check whether the network system is in the expected state (e.g.: it ends up in a state where it believes two peers correctly connected to start a multiplayer match...)
- The framework/tool sets up a recording system, which records the audio output of the OS (e.g.: look up "Stereo Mix" for windows + there is already a bunch of free/open tools for this), runs some code (THE TEST-CASE), and checks whether it is silent or your test did actually make a sound.
Important:
- When testing, most of the principles apply as in the case of software development:
WORK SMART, your tests and test-framework is just as much a software as the software you are testing. It should be flexible, tidy, elegant. Individual tests themselves are dispensable, but the testing system(s) should be rock-solid and well architected.
- Find the important/critical parts and test the hell out of those:
Do not test every miniscule part, especially not on every level, e.g.: if you tested lower level systems correctly, you should already be trusting those parts, no need to test every single time in your game when you try to make a sound and draw a sprite if it works. If a sound cue is really important in the game-play logic, simply MOCK the sound system, and voila, write a simple unit test to check whether the given part of the game-play logic actually calls the sound system...
- Only test and automate stuff which will make a net return:
Testing a software can never be finished only abandoned, just as the development of the tested software. Check whether the testing you do will make a return regarding the time it takes to create and automate the tests. Usually this is a no-brainer in case of unit testing, as it takes only a fraction of the development and "manual" testing (trying out your fresh code with a debugger) time to write the unit tests for a bunch of functions, but with higher level tests it is not the case. These abominations may take a lot of time to make/write :lol:, but may not catch any bugs during their life-time. That is why in case of higher level testing risk analysis and stuff like that is done up-front, and mostly the critical/important and positive outcome (the common use-cases) is tested.
Shameless self-promotion:
I made a record & replay (input recording + replaying and screen-shot comparison) based testing framework for my games. Works like a charm, but took some time to pull it off, but as I know there are off-the-shelf solutions too for this problem, though may not be tailored for games.
If you are interested in the topic, more about it here: Part 1, Part 2
I hope this helped!
Br.