SFML community forums

General => General discussions => Topic started by: Azaral on February 04, 2014, 03:40:30 am

Title: Unit Testing
Post by: Azaral on February 04, 2014, 03:40:30 am
I just recently came across this. I only have a vague idea of what it is. I have no idea how to actually do it, but I would like to give it a try, but I have no idea where to being or even what is really going on with one. Any kind of information you could give me would be great.
Title: AW: Unit Testing
Post by: eXpl0it3r on February 04, 2014, 08:11:01 am
Well in our age it should be clear what to do when you've no idea on a topic: google it! Start with Wikipedia then look up different unit testing tools (for C++), then write some tests.

You might also find TDD (Test Driven Development) interesting.

Personally, I've only ever worked with boosts unit testing. ;)

*summons MorleyDev*
Title: Re: Unit Testing
Post by: Azaral on February 04, 2014, 03:25:52 pm
I have been googling it. I was hoping to get some other info from other people. I tend to not be very good at finding good information with google lol.
Title: Re: Unit Testing
Post by: eXpl0it3r on February 04, 2014, 03:53:37 pm
Well if you have a specific question ask away. For a general definition and overview, we can't really beat Wikipedia (https://en.wikipedia.org/wiki/Unit_testing).

There are many ways to implement/use unit testing, the basic idea is that for every "unit" you write, you a test and at best you even write the test before writing the code. And a "unit" is just the smallest testable code part. ;)
Title: Re: Unit Testing
Post by: wintertime on February 04, 2014, 09:41:14 pm
Most you can read about unit-testing is dry stuff. But there is a simple example for the simplest test library I found (very easy to write tests, no manual registering of tests needed), which I am using: http://unittest-cpp.sourceforge.net/money_tutorial/ (http://unittest-cpp.sourceforge.net/money_tutorial/).
Btw., they moved to github if you want to try the newest version: https://github.com/unittest-cpp/unittest-cpp (https://github.com/unittest-cpp/unittest-cpp).
Title: Re: Unit Testing
Post by: MorleyDev on February 05, 2014, 06:39:15 am
TDD is very nice. I've worked in the business industry at a firm that did extensive TDD from high level acceptance testing right down to the lowest of unit tests (creating a fractal of tests (https://skillsmatter.com/skillscasts/2300-fractal-tdd-using-tests-to-drive-system-design)). It's incredibly effective when done right, when you allow the tests to shape your code instead of trying to hammer the tests to fit your designs.

I'm a pretty big advocate of the practice, and that it's not trivially mockable is one of my biggest gripes with SFML (but that's a whole other can of worms).

UnitTest++ is nice but showing it's age and era, as is GoogleTest and Boost.Test. Personally I'm an advocate of the more recently released libraries (and even wrote my own, http://github.com/MorleyDev/UnitTest11) since they take advantage of C++11 features like lambdas, and as such remove the macro-heavy uglyness of older testing libraries.

http://banditcpp.org/, for example, looks very nice and I'd recommend it over my own library. It's better documented and looks prettier, I've not had time to do much work on mine lately and need to get back to it at some point.

The one advantage I think my UnitTest11 has that I should probably push more is it comes with a pretty decent mocking library integrated into it. It doesn't automock since this cannot yet be done cross-platform. I would love for a new version of C++ to include compile-time reflection that would allow this functionality...
Title: Re: Unit Testing
Post by: Tank on February 05, 2014, 11:35:15 am
I'm also a big fan of test-driven development. Since I learned what it is and how it works, I haven't been programming in any other way than TDD.

I wrote two paragraphs about testing in general and TDD in one of my GDD articles, which you can find here: http://stefan.boxbox.org/2013/02/10/game-development-design-2-good-habits-techniques-and-workflows/

As for the framework, I personally have deeper experience with Boost's Unit Test Framework (http://www.boost.org/doc/libs/1_55_0/libs/test/doc/html/index.html) and CATCH (https://github.com/philsquared/Catch), whereas I love the latter one! (As a bonus it's header-only!)

I've tested a lot of other frameworks, for example CppUnit and Google's Test, but I found almost all of them to be way too specific in their features. For example it's completely unnecessary to have different comparison functions for "greater than" and "equals", instead C++ operators should be used. Also I really don't like a lot of unit testing boilerplate code. In my opinion unit testing should concentrate on the test cases, and not on how to design them.

"Bandit" (mentioned by MorleyDev, thanks!) sounds interesting, but I personally haven't looked at it in detail yet.

One thing's for sure, though: Once you get into TDD, you will never leave it again -- at least without feeling bad. :P
Title: Re: Unit Testing
Post by: Azaral on February 05, 2014, 04:51:49 pm
Thanks for sharing the Catch testing tank. I'm giving it a go in a little simulator I'm making for some physics stuff. So far I'm really liking it. I can know for certain that my functions are doing what I want them to do without having to run the program and see results.
Title: Re: Unit Testing
Post by: Tank on February 05, 2014, 05:30:00 pm
Yes, and whatever you change, you can always make sure that nothing breaks, at anytime (this requires an ideal code coverage of 100%, of course; which you can reach by doing TDD, i.e. by only writing code for existing tests, not the other way around).
Title: Re: Unit Testing
Post by: Nexus on February 05, 2014, 09:16:37 pm
Yes, and whatever you change, you can always make sure that nothing breaks, at anytime (this requires an ideal code coverage of 100%, of course; which you can reach by doing TDD, i.e. by only writing code for existing tests, not the other way around).
Isn't this a bit idealistic? You can never cover everything in tests, and waiting for all possible tests to be written before a feature is implemented is only feasible in simple cases. You can exactly write tests for the functionality you know you're currently testing, but bugs often appear in unexpected places (such as when you combine multiple features, and only in rare special cases). I just think given a certain complexity, it's unreasonable to assume complete bug-freeness, but of course you can reduce them to a minimum with techniques like TDD.
Title: Re: Unit Testing
Post by: eXpl0it3r on February 05, 2014, 09:42:08 pm
Isn't this a bit idealistic? You can never cover everything in tests, and waiting for all possible tests to be written before a feature is implemented is only feasible in simple cases. You can exactly write tests for the functionality you know you're currently testing, but bugs often appear in unexpected places (such as when you combine multiple features, and only in rare special cases). I just think given a certain complexity, it's unreasonable to assume complete bug-freeness, but of course you can reduce them to a minimum with techniques like TDD.
I guess it's important to understand that 100% code coverage doesn't mean the application is bug free. Yes you can never account for all possible things that could happen, especially when it comes to user errors, but the idea behind TDD is, that you don't write code to test, but you write tests to code. That way every piece of code you end up with has a test to it (or rather the other way around), which essentially means 100% code coverage, but still doesn't guarantee that there are no bugs, the chances of unintentional bugs just get reduced quite a bit and since you have all these tests, figuring out what is not being tested is quite a bit easier.

Personally, I think it sounds awesome, but it often seems like so much work, plus there's always that thing, that you can't really test, especially for games...
Title: Re: Unit Testing
Post by: Tank on February 05, 2014, 09:55:53 pm
Quote from: Nexus
Isn't this a bit idealistic?
No, absolutely not.

Quote from: Nexus
You can never cover everything in tests
You can, and by applying TDD, you choose a workflow that even forces that. There are features that can't be covered by automated unit tests though, for example graphical things. Those have to be tested by humans but  are also usually not critical.

Quote from: Nexus
and waiting for all possible tests to be written before a feature is implemented is only feasible in simple cases
I think you got the idea of TDD wrong. You don't write all tests beforehand. You write one single test case, and then you write the implementation, like this:

// Test case:
test( sum( 1, 2 ) == 3 );

// Implementation:
int sum( int a, int b ) {
  return a + b;
}

One rule is that you only write code in the implementation that's being covered by the test cases. If it's not covered, you have to write another test first.

Quote from: Nexus
but bugs often appear in unexpected places (such as when you combine multiple features, and only in rare special cases)
And that's what unit testing avoids: Having unexpected bugs. If code is covered, there's no way you can miss a bug. You can still write wrong test cases, but here the following applies: Do you want to not do testing and accept a high chance of bugs, or do you want to do testing and accept a very low chance of writing wrong tests? If something's goes wrong, you've got a problem in both cases, but with unit testing it's minimized.

When you combine multiple features you do so called "integration tests", either with real objects or mock objects. It's all testable.

Quote from: eXpl0it3r
the chances of unintentional bugs just get reduced quite a bit and since you have all these tests, figuring out what is not being tested is quite a bit easier.
What do you mean by "unintentional bugs"? Let's assume theoretical 100% code coverage, how should a bug happen? The only chance something goes wrong is when you write a wrong test case -- but that's not a bug, it's a logic error.

Quote from: eXpl0it3r
Personally, I think it sounds awesome, but it often seems like so much work, plus there's always that thing, that you can't really test, especially for games...
Writing unit tests means work, yes. Robert C. Martin says "Professional programmers want to make sure that their code works", and I fully agree to that. The result is software tests. And by the way, software tests are also fun once you get used to the workflow.

The situation for games is not really different than for other application types. You can test nearly everything, except graphics effects and such. Untestable things are the rare cases.
Title: Re: Unit Testing
Post by: wintertime on February 06, 2014, 12:11:32 am
I think you can still have bugs if you have covered 100% of the code with some test, but did not handle 100% of the functionality in the tests.
// Test case:
test( sum( 1, 2 ) == 3 );

// Implementation:
int sum( int a, int b ) {
  return 3;
}
 
Oh yeah, I read you should only add the simplest thing to satisfy the test. :P
Also you would be supposed to make the test fail before making it pass. And then add more tests to get to the real implementation.
Somehow its easiest for those simple functions that always get put into examples and much work for something that rarely could get wrong. But the things you would gain most from having tests for are also the most difficult to write the tests for (thats what I'm still struggling with).
Title: Re: Unit Testing
Post by: Kojay on February 06, 2014, 08:02:54 am
The benefits of TDD are well established. On the other hand, I 'm sceptical/unclear on how one is meant to aim for 100% coverage when developing a game, due to visual aspect. How do you verify that what you mean to draw really is drawn? As in gui design, running the application as a user is a far more straightforward way to test (by the way, correct me if I 'm wrong Tank but SFGUI doesn't got a test suite; instead the examples are used as tests).

Incidentally, there is a new version of Boost.Test that only requires docs to make it to release - though I have not tried it and don't believe it would make use of C++11.
Title: Re: Unit Testing
Post by: Tank on February 06, 2014, 09:25:23 am
Quote from: wintertime
int sum( int a, int b ) {
  return 3;
}
Oh yeah, I read you should only add the simplest thing to satisfy the test. :P
That's true, but why on earth do you want to trick yourself? ;-) That's what a lot of people get wrong as well: Unit testing doesn't mean to find ways to make everything fail, it's there to make sure that the stuff you write works as expected.

Returning a constant 3 in a function named "sum()" is clearly a logic error. If that's what you intended, then it's 100% bug-free. Again, illogical stuff is not a bug!

Quote from: wintertime
Also you would be supposed to make the test fail before making it pass.
Yes, that's true and important.

Quote from: wintertime
But the things you would gain most from having tests for are also the most difficult to write the tests for (thats what I'm still struggling with).
Can you give an example? I'm willing to help. :)

Quote from: Kojay
how one is meant to aim for 100% coverage when developing a game, due to visual aspect. How do you verify that what you mean to draw really is drawn?
The actual rendering itself is a very small part of the whole visual thing. For example you can test that positions, properties, blending modes and whatnot of your rendering objects are as you would expect. The final image has to be approved by human eyes; if you then continue by grabbing a screenshot of the result and use that for comparing against future final results is up to you, but for some things it's really okay to move them to the acceptance test area. ;-)

Quote from: Kojay
correct me if I 'm wrong Tank but SFGUI doesn't got a test suite; instead the examples are used as tests
It's correct. I've started SFGUI when I wasn't into software tests. And I can tell you that so many bugs could have been avoided by doing tests. Besides of that it's really time-consuming and annoying to run through all the examples (often the "test" program is enough, because it literally contains everything), click everything, check everything, look at everything etc. Side effects happen, and they are dangerous (they happen less with TDD, because you design differently; but if they happen, you usually detect them through failing tests somewhere).
Title: Re: Unit Testing
Post by: MorleyDev on February 06, 2014, 10:10:48 am
I like to explain it as, whenever I write code I want to see it work and see it work immediately. Testing something inside the running program can take a lot of effort and time, firing up the program and getting it to the exact scenario and point that triggers the code, and so is done when the feature is all written to make sure it's actually working.

But along the way, I'm a serial recompiler. I want to see everything compile and work. Now, to do that quickly I want to isolate the code as I'm writing it and call the functions to make sure they behave as expected. So pre-encountering TDD I would be keeping a separate main function that let me run what I was working on and see it work.

Effectively, I was constantly writing and throwing away unit tests. Post-TDD I have a framework that helps me write and keep those tests, so I can keep them to document the expected behaviour of those functions and even run them when I make further changes to make sure I haven't broken any past behaviour (and naturally I can modify them when I want to break past behaviour).

It also helps that practising stricter TDD tends to self-enforce a lot of SOLID principles (http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)). The real trick comes in learning to 'read the tests', namely accepting that if testing is becoming difficult there's a reason for that. Testing becoming difficult is a code smell (http://en.wikipedia.org/wiki/Code_smell), and often is indicative of code hitting a point where it must be refactored and evolved.

And whilst very useful for business applications, client/server applications especially, for programs like games ATDD (automated testing of your acceptance criteria/high level specifications) is dark witchcraft and may actually be more effort than it's worth. I have heard there is a whole team at Microsoft dedicated to writing AIs to test people's games via friend-of-a-colleague, but that's really all I know on the subject.


The tricky stuff for me comes in how testing integration with 3rd party libraries is something of an art-form in and of itself. I've yet to find a way I'm happy with, especially for C++.

In languages like C# and Java there are libraries that (ab)use the reflective features of the language to let you mock out real classes instead of interfaces, and C++ has a couple of libraries that abuse the low level memory access and inherent understandings of how specific compilers work to achieve a similar result but it's not cross-platform and can break from compiler version to compiler version (especially if you like to work with the latest and greatest of everything).

Typcially the solution involves a facade later around the 3rd party library that must be extensively tested manually, or in passing delegates/individual functions (via std::function) that are easier to mock and test. Neither of these are ideal but they are the lesser evils in my mind, at least for C++.
Title: Re: Unit Testing
Post by: Tank on February 06, 2014, 10:14:19 am
Nice writing, and full ACK.
Title: Re: Unit Testing
Post by: wintertime on February 06, 2014, 02:53:44 pm
Quote from: wintertime
int sum( int a, int b ) {
  return 3;
}
Oh yeah, I read you should only add the simplest thing to satisfy the test. :P
That's true, but why on earth do you want to trick yourself? ;-) That's what a lot of people get wrong as well: Unit testing doesn't mean to find ways to make everything fail, it's there to make sure that the stuff you write works as expected.

Returning a constant 3 in a function named "sum()" is clearly a logic error. If that's what you intended, then it's 100% bug-free. Again, illogical stuff is not a bug!
Thats not about deceiving oneself. :) From what I read about TDD its good to remind oneself when the test is not specific enough to warrant the full real implementation if you can cheat it that easy, then add one or two more tests to improve coverage (like with bigger or negative numbers in that example) and only then add the real thing.
Quote from: wintertime
But the things you would gain most from having tests for are also the most difficult to write the tests for (thats what I'm still struggling with).
Can you give an example? I'm willing to help. :)

I dont have a concrete example atm. One thing is when its something that needs to call other functions with sideeffects or methods on classes which are non-virtual or the class did not derive from an abstract baseclass or like mentioned above interacting with a third party library.
I guess most can be wrapped up in a new class and then some baseclass and a stub/mockup written and calls checked, but that seems like very tedious tripling of the needed code. Or maybe adding extraneous callbacks everywhere just to make it easier to stub something out could help, but that feels so wrong.
Title: Re: Unit Testing
Post by: Tank on February 06, 2014, 02:57:58 pm
I see. ;) Well, if a function is used wrong, I tend to catch that with assert()s, and not by writing test cases that check for errors. I only check for success. ;)

Doing integrations tests can be indeed time-consuming, but it's often (not always) a good sign of the need of refactoring if the task is going to be extremely massive. It's different with 3rd party libraries, but to be honest I haven't had to do much stuff in that regard yet.
Title: Re: Unit Testing
Post by: Lo-X on February 15, 2014, 04:19:20 pm
Hi Tank,

As a user of unit tests with PHP I wanted to use them as well with my C++ jukebox project, even if it is already started.

But the project as a major GUI aspect, so I don't see very well what kind of unit test I can do. As a matter of fact, I've only planned tests on model classes that play with the SQL database. I don't see well other tests I could make.

Anyway, thanks for the links you provided.
Title: Re: Unit Testing
Post by: MorleyDev on February 15, 2014, 05:47:01 pm
But the project as a major GUI aspect, so I don't see very well what kind of unit test I can do. As a matter of fact, I've only planned tests on model classes that play with the SQL database. I don't see well other tests I could make.

Testing the GUI can be automated through a few tools. First of all, you can test everything after an action with the standard methods of dependency injection. Your GUI code should be as shallow as possible anyway (bolded for emphasis). The GUI layer is only responsible telling your underlying system what to do or being told what to do by your underlying system, and those things can and should be testable.

As for testing actual GUI actions (i.e a click), usually that is preserved for more end-to-end tests (like what one would do with Selenium) and depends on the system. I'm not familiar with how to do so on Windows, but this wikipedia page is probably a start. (http://en.wikipedia.org/wiki/List_of_GUI_testing_tools)

On Linux, however, this is actually surprisingly simple. You can expose your GUI to the accessibility layer and drive your tests through that. (http://en.wikipedia.org/wiki/Linux_Desktop_Testing_Project) People should be doing this anyway for blind users. By making it testable you also increase the number of people who can use your software. Win win.

For testing SQL interaction, there are a few ways to do it. End-to-end and integration tests often involve using a local SQL server you control, building the server up to contain the information you want at the start of the test and cleaning it at the end. There are probably some SQLite based (http://www.sqlite.org/) libraries for this, but if you can't find any SQLite alone should cover that need.

For unit level tests, you can mock out the SQL server functions and just verify they are called.

In case you can't tell, I'm a huge fan of fractal TDD (https://skillsmatter.com/skillscasts/2300-fractal-tdd-using-tests-to-drive-system-design). End-To-End acceptance tests at the top, and drill down to specification tests one step below and then all the way to individual classes and functions.  Always mocking/stubbing out dependencies as you go to the degree that level calls for.

At the highest level, you mock nothing. These tests make sure it all fits together. At the specification level, you stub externals such as servers and databases. Your specification calls for you to interact with them in certain ways, you aren't testing they behave as expected at this level. At the unit level, you mock out anything that isn't inside the class/function being tested. Your classes specify they behave in certain ways, if it's not the class you are currently unit testing you don't care if they actually do or not because their unit tests will guarantee that.
Title: Re: Unit Testing
Post by: Lo-X on February 15, 2014, 06:11:20 pm
Thanks, I'm on linux so I'll look at it =)