Seeking advice on unit testing/integration testing

Hi there,

I made an unbeatable tic-tac-toe game a while back and I thought it would make a good project to learn code testing. I have written some tests, but actually trying it has lead to some questions about implementation.

The part I’m particularly concerned with adding tests to is the logic that determines what move the computer should make based on the game state. I’m struggling to decide what would constitute a unit of work. The structure of this logic, in broad strokes, is someting like:

| calculateCompMove() 
|    | subfunction 1 of calculateCompMove() 
|    |    | helperFunction1
|    |    | helperFunction2
|    |    | helperFunction3
|    | subfunction 2 of calculateCompMove() 
|    |    | helperFunction1
|    |    | helperFunction2
|    |    | helperFunction3
|    | subfunction 3 of calculateCompMove() 
|    |    | helperFunction1
|    |    | helperFunction2
|    |    | helperFunction3

The helper functions are pretty small, 1-4 lines usually.

  • Would it be trivial to test these?
  • Should I just unit test subfunctions and calculateCompMove? If so, would you describe the testing for calculateCompMove as integration testing because it is testing the integration between all the subfunctions?
  • If the helpers are only 1-4 lines and not repeated elsewhere in my codebase should they even be extracted out as separate functions?

You can see an example of one of the subfunctions and it’s helper functions here.

Thanks!

It should be: if it isn’t then there is likely to be a problem with the design of your code, so the tests will be doing their job

Ish. If a piece of code won’t work without one or more other pieces of code, then very often yes. It may also, like above, be an indicator that there’s an issue with the design.

This is up to you, you need to judge this. It is often fine to inline the code (and if that happens, the above judgement that they aren’t unit tests changes). And it is often useful to break it out into separate functions. But it is entirely context-dependent.

Basically, don’t at all feel bad about testing very small units. Then work up from there. If you are having difficulty testing stuff, then there may be an issue with the code, and the testing is working.

Anyway, as much as possible to make testing easier: Make your functions idempotent. Pass state in as an argument and return it as the output. Pass non-deterministic values (time, randomness) in as an argument rather than doing stuff inside the function. Avoid conditionals/branching logic wherever you can.

Thanks for the answer Dan, appreciate it a lot!