8

I'm coding a game in my spare time, but I am mostly still a beginner when it comes to programming. I'm sorry if this question is off topic or if it ends up not being helpful to anyone else, but hopefully it will be.

I've spent a lot of time reading books about the design of code, as well as different methodologies and approaches to coding. In the course of this research I keep running into the concept of Test Driven Development. The people who espouse this idea usually seem very passionate about it. I do believe that it can help the speed and efficiency of writing software. Time is a very precious resource so I would rather learn to do things the best way rather than muddle through without trying to expand my knowledge of the programming craft.

Anyway, possibly because I am a beginner, I cannot imagine how to apply test driven development to games. I've found a couple of articles on the subject but they were not very helpful. Most of the examples of unit tests that I have seen are very simple boilerplate examples, or examples from software that is not at all like a game.

In my own coding, I try to approach it with a similar mindset to test driven development, although I'm sure it does not actually qualify as TDD. I write the minimum amount of code to try to implement whatever feature I am adding to the game, and then I immediately test the game to see if it works. If things don't happen as I intended, I immediately make changes to get it closer to what I want. If it is not working or broken, and if I cannot find the bugs by reading the code, I step through the methods in the debugger until I find the unexpected behavior, and then I remove it. What I am trying to say is that I am constantly testing the game, pretty much after each and every incremental change. Testing drives my development. The "unit test" is in my head, because for example I know what the unit in my game is supposed to be doing, so I test to make sure that it is doing it, and if not, I try to fix it right away.

On to my actual question. How can one write unit tests for a complex game? By complex, I mean with many emergent aspects of gameplay, such that the meat of the gameplay emerges from the interaction between the many different elements inside the game combined with the player's choices. For example, a roguelike rpg with a procedural world. What kind of unit tests would one write for a game like that? How could one apply test driven development to such a game?

  • 2
    This king of question might be better suited to the Game Dev section: gamedev.stackexchange.com – glampert Jul 19 '14 at 19:24
  • 1
    It's worth noting that test driven development encourages you to break your design into small manageable units. I use unit tests to test logic which may or may not even be used for the functionality in the project, instead sometimes it's purpose serves to test the concept itself. Look for mature open source games for good working examples of how unit testing can be used in games development – Keldon Alleyne Sep 18 '14 at 0:27
12

Unit tests don't test gameplay. There's no programmatic criteria to see if a game is fun, or a level is the right difficulty. Unit tests will test that your roguelike mapgen actually produces a level with a stairs up and a stairs down. It will test that your encumberance rules are setup that your character actually moves slower when weighted. It will make sure your character can't wear 50 hats. It will make sure that the mechanics are all implemented correctly in relative isolation.

  • 1
    Your answer helps me to understand things a little better, but not completely. From my understanding, for it to really be test driven development, the tests should be written first. So write failing tests, write code to make it pass, then fix the code. How could this be applied to testing whether a mapgen produces a level with stairs up and down? What kind of unit test would meet the TDD requirements? – bazola Jul 19 '14 at 15:31
  • 1
    @bazola - I'm afraid I don't understand? You write a test to call the mapgen, then check to see if the result has stairs up and stairs down. That doesn't compile since you have a mapgen nor the concept of stairs. So you make those. Then you make the mapgen actually meet the criteria... – Telastyn Jul 19 '14 at 15:34
  • That makes sense. Much appreciated! – bazola Jul 19 '14 at 15:43
  • 4
    Relevant: TDD doesn't necessarily mean your unit tests are the sole driver in development. You can start the "drive" by writing a high-level failing "acceptance" and/or integration test, out of which you might conceive a solution and write your first failing unit test ... though, I'm honestly not sure what it takes to write a meaningful, automated acceptance test for a non-trivial game. – svidgen Jul 20 '14 at 2:37
2

It is hard to write unit tests for code that is non-deterministic. If you have code involving random numbers, you won't be able to write a unit test that asserts an expected result.

So, unit tests are more appropriate for the code that is deterministic. When I give a method these inputs, I expect these outputs. As an example: when a fighter with 15 strength uses his two-handed broadsword to successfully hit an undead ghoul with 10 armor, I expect 8 damage. The non-deterministic part (whether the hit was successful or not) can't necessarily be unit tested, but what happens after that can be. Even if the amount of damage is expected to be within some range, you can test for that.

If you're having a hard time finding code to write unit tests for, you need to refactor so that the deterministic logic is isolated in methods or classes. Roll the dice to first to determine success, then pass the relevant inputs (class, strength, armor, etc.) to a testable method.

To your comment about having the unit test in your head, the point of unit testing isn't just about freshly written code. It's also about having confidence that the code still works after making inevitable changes.

  • 3
    One way to unit-test code which uses randomness is to run it lots of times and check if the results statistically conform roughly to the expected distribution. So when an attack is supposed to do 10+2d6 damage, you run the code 1000 times and check that there is no result larger than 22, no smaller than 12 and that there is a roughly bell-shaped distribution within that range. – Philipp Sep 17 '14 at 18:48
  • 1
    You can also apply this method to more complex situations, like making sure that a level 1 character wins a simulated fight against a goblin about 90% of the time. – Philipp Sep 17 '14 at 18:54
  • 3
    Normally if part of your code uses random numbers, you abstract out your random number generator into a service (IRandomNumberGenerator) and anything that needs to use random numbers accepts a reference to that service interface in their constructor. Then when your test creates that object to run a unit test, it injects a MockRandomNumberGenerator that feeds a fixed known stream of numbers out. Then you have a repeatable test. The same idea is useful for anything that needs to get the current date/time or downloads information from a web service - just inject a mock service. – Scott Whitlock Sep 17 '14 at 19:01
  • ...then you can write a separate test that uses statistical methods to check the actual workings of your real implementation of RandomNumberGenerator. – Scott Whitlock Sep 17 '14 at 19:03
  • I would downvote if I could. Statistical features of the result shold be tested for the random algorithm. – Riga Oct 17 '14 at 7:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.