So with the little unit testing I've read about, I decided to try using it. So far, it's worked well and I'm finding myself to be more productive.
However, one of the main objectives of it is that each test should be as isolated as possible. This is where I'm starting to wonder about my code.
For example, my Map class has 2 tests, testSetTile and testGetTile. testGetTile is perfectly fine. However, I am finding it incredibly difficult to implement testSetTile without using GetTile in the test - thus linking them and testing more than one thing in one test (so I wouldn't be able to see the correct error).
Now, I'm about to do the unit tests for my Camera. Again, I'm finding it a physical impossibility to make tests for this without having to use the Map class. Which isn't exactly isolated at all - I can't tell if the errors are with the Camera or the Map.
Should I just ignore these things, and just do the best I can? Or is there a simple solution?
Unit tests not completely isolated
How do you test get tile with out first calling set tile? Setting tiles through a constructor perhaps? I would consider the unit under test the class and not be to worried about using multiple methods from one class in a test. I would certainly try to limit them to as few as possible but on the whole it isn't that big a deal.
Now for the objects interacting. In straight up unit testing you would use Mock or stub objects to test with(this won't be hard if you have been following the Dependency Inversion Principle). Personally I don't care if my code works with faked out bs I want to know if it works with the rest of my code so I always try to avoid Mocks or Stubs, and try to limit object interaction as much as possible (this makes my automated tests not unit tests, but I don't really care).
Now for the objects interacting. In straight up unit testing you would use Mock or stub objects to test with(this won't be hard if you have been following the Dependency Inversion Principle). Personally I don't care if my code works with faked out bs I want to know if it works with the rest of my code so I always try to avoid Mocks or Stubs, and try to limit object interaction as much as possible (this makes my automated tests not unit tests, but I don't really care).
I used to fret about things like this too, but a couple of things to consider:
In the latter case, the tests don't actually communicate as much. What does it mean to work? While it is indeed a good idea to aim to test each method/function/etc in isolation from any other, your tests must reflect actual usage else they're pointless. There is simply no point in having a SetTile() method unless there's also a GetTile(). So calling them in the same test is entirely natural and better documents their behaviour, I'd say.
Coding to interfaces/abstractions rather than implementations helps here (at least in parts that aren't super-performance-sensitive).
But in your specific case of the Camera and a Map, why are they so tightly coupled? If your game is in 3D, the Camera might store some FOVs, an affine transform and maybe some clip distances. It might have methods to generate a projection matrix and world-to-view transform. That's entirely testable. There's an analogous situation in a 2D game, I'm sure.
// Do this:TEST("SetTile() adds tile retrievable by GetTile()"){ // ...}// rather than this:TEST("GetTile() works"){ // ...}TEST("SetTile() works"){ // ...}
In the latter case, the tests don't actually communicate as much. What does it mean to work? While it is indeed a good idea to aim to test each method/function/etc in isolation from any other, your tests must reflect actual usage else they're pointless. There is simply no point in having a SetTile() method unless there's also a GetTile(). So calling them in the same test is entirely natural and better documents their behaviour, I'd say.
Quote:
Now, I'm about to do the unit tests for my Camera. Again, I'm finding it a physical impossibility to make tests for this without having to use the Map class. Which isn't exactly isolated at all - I can't tell if the errors are with the Camera or the Map.
Coding to interfaces/abstractions rather than implementations helps here (at least in parts that aren't super-performance-sensitive).
But in your specific case of the Camera and a Map, why are they so tightly coupled? If your game is in 3D, the Camera might store some FOVs, an affine transform and maybe some clip distances. It might have methods to generate a projection matrix and world-to-view transform. That's entirely testable. There's an analogous situation in a 2D game, I'm sure.
Quote:Original post by the_edd
I used to fret about things like this too, but a couple of things to consider:// Do this:TEST("SetTile() adds tile retrievable by GetTile()"){ // ...}// rather than this:TEST("GetTile() works"){ // ...}TEST("SetTile() works"){ // ...}
In the latter case, the tests don't actually communicate as much. What does it mean to work? While it is indeed a good idea to aim to test each method/function/etc in isolation from any other, your tests must reflect actual usage else they're pointless. There is simply no point in having a SetTile() method unless there's also a GetTile(). So calling them in the same test is entirely natural and better documents their behaviour, I'd say.Quote:
Now, I'm about to do the unit tests for my Camera. Again, I'm finding it a physical impossibility to make tests for this without having to use the Map class. Which isn't exactly isolated at all - I can't tell if the errors are with the Camera or the Map.
Coding to interfaces/abstractions rather than implementations helps here (at least in parts that aren't super-performance-sensitive).
But in your specific case of the Camera and a Map, why are they so tightly coupled? If your game is in 3D, the Camera might store some FOVs, an affine transform and maybe some clip distances. It might have methods to generate a projection matrix and world-to-view transform. That's entirely testable. There's an analogous situation in a 2D game, I'm sure.
I am trying to code to abstractions rather than implementations. There is no Camera class at the moment, and the Map has only the implementation to pass the couple of tests it crossed my mind to do.
And the tightly coupled bit is that the functionality of the Camera would be to store a position and then return indices of viewable tiles of the map. So it would need a Map object (with working GetTile()) to test that a)It returns tiles. b)It can move and return the right tiles.
In any nontrivial code you will run into cases that you can't unit test with a single class; there will inevitably be situations where you need multiple objects as part of a test.
IMHO there's nothing wrong with coding unit tests that involve multiple objects, specifically because it's perfectly natural in real code to have interactions between many objects. In fact, it usually helps to write unit tests that do test your interactions between objects, precisely as you encountered in your Camera/Map situation.
Structuring your tests to handle multiple objects takes a bit of getting used to, and depends heavily on how your unit test harness is set up; but it's not a "bad thing" to have such situations in your tests at all. I'd go so far as to say that being comfortable with writing tests that span objects is a vital tool in the unit testing toolbox.
IMHO there's nothing wrong with coding unit tests that involve multiple objects, specifically because it's perfectly natural in real code to have interactions between many objects. In fact, it usually helps to write unit tests that do test your interactions between objects, precisely as you encountered in your Camera/Map situation.
Structuring your tests to handle multiple objects takes a bit of getting used to, and depends heavily on how your unit test harness is set up; but it's not a "bad thing" to have such situations in your tests at all. I'd go so far as to say that being comfortable with writing tests that span objects is a vital tool in the unit testing toolbox.
Quote:Original post by The Communist Duck
And the tightly coupled bit is that the functionality of the Camera would be to store a position and then return indices of viewable tiles of the map. So it would need a Map object (with working GetTile()) to test that a)It returns tiles. b)It can move and return the right tiles.
It wouldn't need a Map object, necessarily. It would need something that implements an interface that Map happens to implement as well. I'm guessing wildly here, because I don't know the specifics of your game, but you might have something like this:
struct TileGrid // abstract interface{ virtual ~TileGrid() { } virtual unsigned Width() const = 0; virtual unsigned Height() const = 0; // etc...};
Your Map class would then implement the TileGrid interface and you would write tests for the Camera against a TileGrid implementation specifically designed for testing (a kind of mock object), rather than a Map.
This may very well be over the top, but this is what is meant by coding to an interface, rather than an implementation.
I'm generally against introducing interfaces just for mock test objects, actually. If there was a need to generalize into a TileGrid for the game purpose itself, that's one thing, but just having it around to make unit testing a bit more rigidly contained seems pointless to me.
Do the simplest thing that could possibly work.
Do the simplest thing that could possibly work.
Quote:Original post by ApochPiQ
I'm generally against introducing interfaces just for mock test objects, actually. If there was a need to generalize into a TileGrid for the game purpose itself, that's one thing, but just having it around to make unit testing a bit more rigidly contained seems pointless to me.
Do the simplest thing that could possibly work.
FWIW, I tend to agree. I just wanted to illustrate that it's possible.
Make unit tests for rules, not functions. A particular unit test should test exactly one rule, and a particular function will be invoked by one or more unit tests.
So for the ur-example of a bank account, the rules are:
* The CurrentBalance() of a new bank account is 0.
* If a bank account is Credit()ed by an amount, the subsequent CurrentBalance() will be increased by an amount equal to the Credit()ed amount.
* If a bank amount is Credit()ed by an amount and then Debit()ed by a lesser amount, the subsequent CurrentBalance() will be increased by an amount equal to the difference of the two amounts.
* If a bank account is Credit()ed by an amount, and then Debit()ed by an amount greater than the prior CurrentBalance() plus the Credit()ed amount, the Debit() operation will fail.
Of these four rules, three invoke more than one function, and CurrentBalance() is invoked by all of them. However, all of the rules can be tested independently of each other, and do not require each other to succeed first. Additionally, since these rules refer to invariants rather than hardcoded numbers, they can be used in random sequence for fuzz testing.
So for the ur-example of a bank account, the rules are:
* The CurrentBalance() of a new bank account is 0.
* If a bank account is Credit()ed by an amount, the subsequent CurrentBalance() will be increased by an amount equal to the Credit()ed amount.
* If a bank amount is Credit()ed by an amount and then Debit()ed by a lesser amount, the subsequent CurrentBalance() will be increased by an amount equal to the difference of the two amounts.
* If a bank account is Credit()ed by an amount, and then Debit()ed by an amount greater than the prior CurrentBalance() plus the Credit()ed amount, the Debit() operation will fail.
Of these four rules, three invoke more than one function, and CurrentBalance() is invoked by all of them. However, all of the rules can be tested independently of each other, and do not require each other to succeed first. Additionally, since these rules refer to invariants rather than hardcoded numbers, they can be used in random sequence for fuzz testing.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement