Unit Tests Are Awesome! … Sometimes

A couple of weeks ago, I started coding up a serialization system for a side project I’m working on. After thinking about the problem and jotting my ideas down on paper, I walked some developer friends of mine through the design. When I mentioned wanting to use Unit Tests, one of my friends (we’ll call him Sam) said, “Ugh. Why though?”

Now Sam is a smart guy and a great developer. So when he asked me that question, it caught me off guard. Aren’t Unit Tests good? Well, not always. At his current job, they Unit Test EVERYTHING and the managers are pushing for 100% code coverage. Development speed has slowed to a crawl and changing anything winds up breaking several tests that don’t matter. I admitted that sounded terrible. I promised that I was going to do things differently and I’d report back on how things went.

How I used Unit Testing

At first I just used the Unit Test harness as an easy spot to run some one-off code. I wanted to write some of the data to disk so it was easy as marking a method with the Test attribute. Then I could double click the test any time I wanted to execute it. This throw away code quickly turned into my actual SaveFile and LoadFile tests.

Over the next couple of weeks, I got the following things working. Next up were tests for saving basic classes and structs. That felt a bit like busy work, but it wound up being useful as the tests served as templates for other tests. And when I accidentally broke my UniqueObject system, these tests let me know about it. Then SaveTeam/LoadTeam proved out that I could save object hierarchies. SaveTestStateMachine/LoadTestStateMachine showed that I could serialize big inheritance chains with OnComplete delegates and Message Subscriptions.

So Did Unit Testing Help?

Absolutely. My side project is already well under way. Implementing the Save System directly in the existing code with the real data would have added complexity to an already complex problem. Having separate test classes and methods meant I didn’t have to worry about breaking existing functionality while I got the basics of the system working.

It was also very useful whenever I changed how things should work. I had a general idea of where I wanted to go with the Save System, but I changed my mind on design specifcs quite a few times along the way. After a big refactor, I would run my tests. Many times it was all green check marks, but sometimes there’d be a red X showing me where I broke something. Getting the status board back to all green made it so I was confident in my changes.

Image showing several unit tests with green checkmarks.
It’s a good feeling seeing all those green checkmarks.

It even helped me find a couple of tricksie bugs when I was loading a more complex object and the OnCompletes were all getting called twice. Originally, I thought the bug was going to be inside my delegate serialization system. But the individual tests were working fine. Digging a little deeper, it turns out that I was still saving out the OnComplete calls in a derived class and forgot to delete it when I moved this logic up to the base class. This let me focus my debugging efforts on the correct spot instead of wasting time looking at working code.

Now I have a solid system in place and I can wire it up inside my actual project. I’m sure I’ll encounter problems along the way and run into things I haven’t thought of. And when I change the design to accommodate these issues, I’ll have my tests to keep me pointed in the right direction.

When should you write a Unit Test?

  • When you want to have some easily executable throwaway code. – I’ve written plenty of Debug methods and slapped them into main or some other random part of the process that gets triggered. I’ve also accidentally left code like that in and seen log messages with “EckTest – Inside SaveFile()” inside released code. >.<
  • When you want a separate environment to work in. – Starting from a clean slate reduces complexity and makes sure you don’t break things while you develop a new system.
  • When your design isn’t solid, but what you want to accomplish is. – In my case, serialzing something and deserializing it is a known quantity. I want the loaded object to have the correct data. This is also true when writing big refactors for a system that is already working.
  • When it speeds up your development process. – Unit testing is a tool like any other. If it’s speeding up your time to delivery… Use it!

When shouldn’t you Unit Test?

  • Unit testing for unit testing’s sake. – Don’t do this (or anything else really) “just cause”. Have a good reason.
  • When what you want to accomplish is unknown. – If every time you change your mind you have to change the test too… That’s probably a bad thing to be unit testing. If you aren’t sure what you want to happen, you aren’t ready to write a unit test for that thing yet. Come back later when you do.
  • When it slows down your development process. – Unit testing is a tool like any other. If it’s slowing down your time to delivery… DON’T use it! It’s worth mentioning that writing Unit Tests does take time. But it’s an investment. You need to weigh the time it will cost against how much time you might save. Odds are, probably yes.

Tips from your Uncle Eck

  • Test the concepts – not the facts. – Test that the damage system works. Don’t write a test that your Autocannon does 5 damage. Designers will change their mind and break a whole suite of tests.
  • Test WHAT your code should do, not HOW it does it. – How something works might change several times over the project. It isn’t worth testing unless the “how” is an actual requirement.
  • Use a human readable format during dev. – It saves sooo much time. I can read my save files, and all my UniqueObjectIDs. If this was a binary file with guid IDs, I’d have to jump through a ton of hoops to be able to untangle problems. And if you need a binary format at the end of the project, you should be able to swap things out without too much trouble.
  • All absolute statements are flawed (except maybe this one). – If someone tells you to ALWAYS use Unit Tests or to NEVER use Unit Tests, they’re probably wrong. Use them when they’re the right tool for the job.

Finite State Machine Refactor – Take 2

I finally came back to my Gaslands side project after about a year. For those that don’t know, Gaslands is a turn-based table-top miniatures game where you modify toy cars to look like Mad Max vehicles. The goals of a game changes based on the scenario, but in general you move around with special Maneuver Templates and shoot each other’s vehicles with various weapons. Check out the Table-Top game here: https://gaslands.com/

When I left off last time, I had coded up a Finite State Machine and started putting it to use. Here’s a link to the previous article: https://www.gamedev.net/blogs/entry/2274204-finite-state-machine-for-turn-based-games/ After that post, I got most of the Movement Step working. Even though it worked, there were still a few problems that made working in it difficult.

File Name Difficulty

When debugging the code, I knew what file the problem was in. But finding out where I was in the sequence (where we came from, where we’re going next) was extremely difficult. The steps during an activation are very sequential, but Visual Studio sorts all the files in a folder alphabetically. As you can see, below, the sequence bounces all over the place making it extremely difficult to have a mental picture of what was going on.

One thing that I did was put a prefix on the filenames that identified its place in the sequence. And for state-machines with substates, I added another _00 to further identify them. This simple trick made it TONS easier to track what was going on. Compare the previous layout to the picture below and see for yourself. If we had a problem in the MoveToTemplateStep, I now know what the previous step is with a simple glance.

Single Responsibility

Another problem I was running into was deciding where certain bits of code should live, and how the different pieces communicate with each other. Originally I had a GameController class that different game states might fire a message off to. Then it would route those to other bits of code, show some UI, perform some game logic functions, and then call back into the game states. Different bits of code were scattered throughout the project. Again, this added complexity to the project and made it difficult to debug.

Here’s an example of how things existed in the previous version. For the SelectManeuverTemplate step, the state machine sent a message to the GameController. It would then show the UI, let the user confirm their selection, and then mark the message as complete. It was responsible for too much so I just cut out the middle man. So now the UI listened for the message directly. Let the user confirm their selection and then called the OnComplete callback. Instead of the GameController managing more states outside of the state-machine, I added more states and everything started flowing more smoothly.

There was also several bits of code that was scattered across the project. This code existed inside the GameController, some UI Classes, and inside the StateMachine steps. I pulled all this code out into a separate GameLogic class. This allowed me to wire them up to a test framework so I could verify their correctness individually. There wasn’t much left in the GameController class at all and I was able to drop it entirely.

Acording to git, my previous commit before this refactor effort was about a year ago. And after three weeks of work, the functionality is back where it was. >.< However, now the project feels MUCH cleaner so it was definitely a good investment. I have a good idea of where new code should go and how new features in the combat game are going to be implemented. And I’m really looking forward to getting back in the code to tackle what’s next.

Next Steps

I’m debating on what to work on next, but I’m leaning towards number 3.

  1. Finish up the Movement Step – I got the rough basics of the movement step/turn structure working, but there’s still plenty to do with the MovementStep. I need to create more UI / player communication instead of just using the Unity Console Log.
  2. Wire up some cool assets instead of my dev art – I also have plenty of Unity assets that I have bought over the years. Hooking up some actual cars/motorcycles would elivate the coolness factor. I might save that for when my interest is waning a bit.
  3. Start work on a SaveGame system – I’m actually leaning towards working on the SaveGame system early. I see so many projects leave that until the very end and it quickly becomes a nightmare trying to shoe-horn it in.

Tips from your Uncle Eck

  1. Complexity is the enemy! – If you find your code base starting to become a pain to work in and difficult to debug, it slows down productivity across the board. Take a step back and refactor it into something more manageable.
  2. Refactoring your code should be part of your dev process. – It does take time, but it quickly pays dividends. A cleaner code base has less bugs, and it’s easier to fix them. It also helps keep your motivation high.
  3. Keep a Notes.txt file in your project and take notes on what you’re thinking/planning to do. It was a year since I worked on this side-project last and it greatly reduced my spin-up time to get back into the swing of things.