What Is a Unit Test in Programming? A Simple Guide
What is a unit test in programming? It is a small piece of code that runs one other small piece of code, the unit, and checks that it gives the answer you expect. The unit is usually a single function or method. The test runs in a fraction of a second, with no network, no database and no screen, and it tells you pass or fail.
This guide explains how a unit test is built, shows a worked example, and then applies the idea to a game with clear scoring rules.
What is a unit test in programming?
A unit test is an automated question you ask your code. "If I give this function these inputs, do I get this output?" You write the question once. The computer asks it again every time you change something.
Three parts make up the definition:
- Small. It targets one behavior, not a whole feature.
- Automated. A test runner executes it and reports the result. You do not click anything.
- Isolated. It does not depend on other systems being up, so it gives the same result on every run.
The word "unit" has no fixed size. A unit is whatever piece you can describe in one sentence, such as "turns a kill count into a score multiplier."
How a unit test works: arrange, act, assert
Most unit tests follow the same three steps.
- Arrange. Set up the inputs and any objects the unit needs.
- Act. Call the function you are testing.
- Assert. Compare the result with the value you expected. If they differ, the test fails and says so.
Keep each test to one act and a small number of asserts. When a test fails, you want the name of the test to tell you what broke. A name like three_kills_give_multiplier_two beats test1.
A simple unit test example
Say you have a function total_price(items) that adds up prices and applies a ten percent discount when the total is over 100. Here is how you would test it in plain words.
- Test one: arrange a list with prices 40 and 30. Act by calling
total_price. Assert that the result is 70, because there is no discount. - Test two: arrange a list with prices 80 and 40. Act. Assert that the result is 108, because 120 minus ten percent is 108.
- Test three: arrange an empty list. Act. Assert that the result is 0.
Notice what these three tests cover. One is the ordinary case, one is the rule that changes the answer, and one is the empty edge. Good unit tests usually come in that shape. You also want a test right at the boundary, such as a total of exactly 100, because boundaries are where off-by-one mistakes hide.
Why unit tests matter
Tests pay off in four ways.
- They catch regressions. You change one rule and an old rule quietly breaks. A test fails the same minute.
- They make changes safer. You can clean up messy code and re-run the tests to see that behavior did not move.
- They document behavior. A test named after a rule, with the exact numbers, is a living example of how the code should act.
- They shorten debugging. A failing small test points at a small area, not at the whole program.
Functions that only depend on their inputs are the easiest to test. If you have read about memoization, the same property is what makes caching safe: same input, same output. That property also makes a test trustworthy.
Qualities of a good unit test
A test can be bad in a few ways. It can be slow, flaky or vague. Aim for these qualities instead.
- Fast. You will only run tests often if they take seconds, not minutes.
- Repeatable. No dependence on the clock, random numbers or the order in which tests run. If the code uses chance, pass in a fixed seed.
- Independent. One test does not rely on another having run first.
- Specific. When it fails, the name and the message tell you which rule broke.
- Tied to behavior, not structure. Test what the function promises, not how it does it inside. Then you can rewrite the inside without rewriting the tests.
Unit tests vs integration tests
A unit test checks one piece alone. An integration test checks that several pieces work together, such as your code plus a real database, or a login form plus the server behind it.
You want both, in different amounts. Unit tests are cheap, so you write many. Integration tests are slower and break for more reasons, so you write fewer and aim them at the seams between parts. A unit test can say the discount function is right. Only an integration test can say the checkout page actually calls it.
Unit testing a game: a Tank City Reboot example
Games look hard to test because they are visual and fast. But under the screen they are full of small rules with exact numbers, and those rules are good unit test targets. Both Tank City Reboot and Tank City Zero Day ship tests in their code that check such rules, written with NUnit, a test library for C#. Zero Day's RulesTests.cs holds 10 of them. The examples below come from that file and from the rules it checks.
Example 1: the kill chain multiplier
In Zero Day, every kill adds to a chain. The multiplier is 1, plus one for every 3 kills in the chain, up to 8. That is a pure function from a number to a number, so it is easy to test. The game's own test is named TheMultiplierGrowsEveryThreeKillsUpToEight, and its asserts are:
- A chain of 0 kills gives 1, and so does a chain of 2.
- A chain of 3 kills gives 2. This is the boundary.
- A chain of 21 kills gives 8, the cap.
- A chain of 500 kills still gives 8, to prove the cap holds.

The name of the test is the rule, in plain words. If someone changes "every 3 kills" to "every 4", the test fails and names the exact number that moved. You could add more boundaries yourself, such as 5 kills giving 2 and 6 kills giving 3.
Example 2: a rule with rounding
Clearing a wave adds a core bonus of 250 times the wave number times the share of core integrity left, rounded. The core starts with 3 integrity. On wave 4 with 3 of 3 left, the bonus is 250 x 4 x 1, which is 1000. With 2 of 3 left, it is 250 x 4 x two thirds, about 666.67, which rounds to 667. A test that asserts 667 teaches a reader that rounding is part of the rule, something a quick play session would never notice. This one is an exercise to try on your own copy of the idea, not a test the game lists.
Example 3: tests that use a fixed seed
Zero Day offers you three different upgrades after a wave, picked by chance. Chance is the enemy of a repeatable test, so the game's test AFreshRunAlwaysOffersThreeDifferentUpgrades builds its random number generator with a fixed seed of 5, then asks for an offer 200 times and checks that each has 3 entries, all different. The seed makes every run of the test identical, and the loop covers many cases in a blink. This is the "repeatable" quality from earlier in practice.
Example 4: checking hundreds of arenas
Zero Day builds a fresh arena for every wave. Nobody could check each by hand, so ArenaTests.cs builds 300 of them, 30 seeds times 10 waves, and asserts for each that it is 13 by 13 tiles of known kinds, that it is a mirror image left to right, and that the gates, the player start and the core are kept clear and fair. A fairness check like that is a search problem, and breadth-first search is how the game does it. The test does not care about the method. It only checks the answer, which is the "behavior, not structure" quality from earlier.
Both games also test their tile maps, and the second one tests its music, so the habit covers more than math. Rules that stay stable while you rebuild the rest are what make tricks like caching safe to add later.
Frequently asked questions
Do unit tests catch every bug?
No. They only check the cases you thought to write, and they test pieces alone. Bugs that come from pieces interacting, or from situations you did not imagine, need integration tests, careful review and real use.
What makes a unit a good size to test?
Pick something you can describe in one sentence and that has clear inputs and outputs, such as a single function. If you cannot name what it promises, it is probably too big or does too many things.
Should I write the test before or after the code?
Both work. Writing the test first forces you to decide what the code should do, while writing it after is fine for existing code. What matters is that the test exists and fails when the rule breaks.
Which tools are used for unit testing?
Most languages have a standard test runner or a popular library, for example pytest for Python, JUnit for Java and Jest for JavaScript. They all provide the same basics: a way to name tests, run them and compare results.
Get started
Pick one small function in your own code that has a clear rule, and write three tests for it: a normal case, a boundary and an empty or extreme case. Run them, then change the rule on purpose and watch which test fails.
If you want rules with real numbers to practice on, play a few waves of the game and note the scoring rules you see, then write tests for them as an exercise. Tank City Reboot is free, plays in your browser on a computer or a phone, and needs no account.
Comments
No comments yet.
Sign in or make an account to comment.