|
||||||||||||||||||
Add Forum to Favorites | Send Topic To a Friend | View Forum FAQ | Track this topic |
Last Thread Next Thread ![]() |
| Test Driving Expression Template Programming |
|
![]() Glass_Knife Moderator - Java Development Member since: 8/8/2001 From: Albuquerque, NM, United States |
||||
|
|
||||
| This is the first online tutorial/article I've read that shows the nuts and bolts of TDD. Thanks for the great job. Glass_Knife I think, therefore I am. I think? - "George Carlin" |
||||
|
||||
![]() DonDickieD Member since: 4/24/2002 From: Munich, Germany |
||||
|
|
||||
| Very nice job. I visited a software engineering at the GDC this year and a lot of companies seem at least to try out TDD and some use it regulary now. By the way, expression objects are discussed in "The C++ Programming Language" and in one of Jim Blinns book ( have to look up the title ) as well. Can you give an example on how to setup unit tests on other classes, like a renderer class? Also how hard shall I force my test code to make the function fail? When implementin a class the goal should be that it can handle a lot of errors, while the test function really shall try to crash the test. Shall I pass bad pointers, out of range indices and stuff like this into a test function? And finally regarding test cases where I have to test mathematical results, for example how would you test a dot product - simply like this: vec3 v1( 1.0f, 2.0f, 3.0f ); vec3 v2(-2.0f, 1.0f, 2.0f Assert ( 6 == dot( v1, v2 ) ); Are there any results when chosing the parameters? How do you manage your projects - do have a test project for every subbsystem? Are there any good tips on managing all the tests? Sorry for this bunch of questions, but I am really interessted in this topic. So maybe you have a minute or two to give a short answer :-) Best regards -Dirk |
||||
|
||||
![]() dot Member since: 7/15/1999 From: Singapore, Singapore |
|||||
|
|
|||||
Quote: Test cases are typically created for verifying logic correctness. Sure, people has taken a step furthur to verify the correctness of visual output(like your renderer case) as well as linkage between states (web pages). However, the unit testing tools at the moment are still rather inadequate for the earlier case (visual output). There are some tools emerging for web pages, but I have yet to try them out, though I have not heard much good on them (at least on the free price range). For a renderer, I would have to abstract it totally devoid of any logic, and use it as it is, purely an abstraction layer over OGL or even D3D. I could perhaps test a few stuff, like creation, enforcing of drawing occurs only within a scene block, etc. I even thought about doing screen captures and then running the test, and then doing a pixel by pixel comparison, except I have not actually tested this concept. (After all devices might differ in the eventual display). Quote: You basically write test cases for every possible success and failure case you can think of. You will definitely not think of a particular test case that will fail, but when you encounter a failed situation, or think of, you should promptly add it in. This is to boost your confidence level of the component as well. The more test case you have, the more confident you are of the quality of your code. You could also have only 1 single test case, but personally I would not be confident of just 1 single test case (unless it is really insanely trival stuff. But even then, I would ponder of ways to break it). Quote: What you shown seems like one of the test case I would choose. I do not really understand this question. In true TDD context, you would actually need two test case that pass, because simply, the first time you did a test case, you would just return the dot result 6, instead of the actual logic. It is the 'smallest simple step that pass the test' that gets considered first. Quote: I try to give every class their own test class. Then for the project/subsystem I have a test project for it. So for development, I only run all the test classes for this sub system. Once I 'finalize' the work (on a daily basis), I run all the test projects (you would want some batch program or even merge them into a single one somehow), to verify that the changes made did not break any existing related or even unrelated components. Then after that, can you safely announce it as a safe stable version. |
|||||
|
|||||
![]() DonDickieD Member since: 4/24/2002 From: Munich, Germany |
||||
|
|
||||
| Thanx a lot. I looked at the links you provided and found some nice articles I will work through in the next days. So just to see if I got it correctly: Let's say I design a vector class and I want to be able to construct a vector from three floats like this: vec3 v0( 1.0f, 2.0f, 3.0f ); The first test simply will be the construction of the vector without the compiler complaining about it. A second test could be if the parameter get assigned correctly, like this: ASSERT( v0.x == 1.0f ); ASSERT( v0.y == 2.0f ); ASSERT( v0.z == 3.0f ); One error I could think of is when a client passes a NAN or infinite float value as argument. Are there unit tests for this case or is this more a design question how I want to handle this exception? All the best -Dirk |
||||
|
||||
![]() dot Member since: 7/15/1999 From: Singapore, Singapore |
||||
|
|
||||
Quote: TDD typically starts off this way. Though in other cases you might not need to care if the parameter are assigned correctly, but as long as the behaviour/result of actions are correct (Like do a multiplication between vectors give you the expected result). TDD if i recall correctly does not dictate black box or white box testing, so it's up to you to decide. Quote: This depends on your design. Should a vector perform input validation? Will the excessive validation slow down the vector operations which might be used 'millions of times' throughout the application? If there are internal validation done, should it be turned off upon delivery via a #define (though this is typical a bad idea for some applications). It all actually boils down to *if* input to vector are controlled fixed values, or unknown runtime determined values. |
||||
|
||||
![]() DonDickieD Member since: 4/24/2002 From: Munich, Germany |
||||
|
|
||||
| Thanks for your help Kent. i think I will become a test addict as well ;-) BTW: Do you use an tools for refactoring? |
||||
|
||||
![]() dot Member since: 7/15/1999 From: Singapore, Singapore |
||||
|
|
||||
Quote: My primary job deals with J2ME, and is using Eclipse as my IDE, which feature quite a robust set of refactoring features. For C++, I am unsure, though I read somewhere that VC.Net 2k5 might feature refactoring tools as well. I'm sure there are commercial refactoring products out there, but I can't say I have used any. |
||||
|
||||
![]() DonDickieD Member since: 4/24/2002 From: Munich, Germany |
||||
|
|
||||
| Hi dot, I started writing my first tests. One thing I am curious about is if you in order to check the state of a class after a function call declare the test class as "friend" to gain data access? I don't necessarily have mutators and accessor for every data element, although I maybe like to test it. A simple example would be to a vector class like this: class Vector2 { public: Vector2( float x, float y, float z ); private: // Data members now private float x, y, z; } How would you test that the parameters get assigned correctly to the data members when you can't access them from outside anymore? -Dirk |
||||
|
||||
![]() Amnesty Member since: 4/30/2002 From: USA |
||||
|
|
||||
| Expression Templates are pretty strange. I had to stare at the code a long time to figure out the magic about them. I find them interesting. However, they don't appear to be optimizing for me :( Here is the my implementation of your array along with a test and results i get. I changed the implementation because i couldnt get yours to compile (no partial specialization on my compiler). Any idea on why its not optimizing? #include <iostream> #include <boost\progress.hpp> template <class Type, std::size_t Size> class array_addition; template <class Type, std::size_t Size> class array_multiplication; template <class Type, std::size_t Size> class array_scalar_multiplication; template <class Type, std::size_t Size> class array { public: typedef Type* iterator; typedef Type value_type; array (const Type& arg = Type()) { std::fill_n (&v_[0],Size,arg); } const std::size_t size (void) const { return Size; } Type& operator [] (std::size_t i) { return v_[i]; } const Type operator [] (std::size_t i) const { return v_[i]; } array (const array_addition<Type,Size>& arg) { for (int i = 0; i < size(); ++i) v_[i] = arg[i]; } array (const array_multiplication<Type,Size>& arg) { for (int i = 0; i < size(); ++i) v_[i] = arg[i]; } array (const array_scalar_multiplication<Type,Size>& arg) { for (int i = 0; i < size(); ++i) v_[i] = arg[i]; } iterator begin () { return v_; } iterator end () { return &v_[Size]; } private: Type v_[Size]; }; template <class Type, std::size_t Size> class array_addition { public: array_addition (const array<Type,Size>& a, const array<Type,Size>& b) : a_(a), b_(b) {} const Type operator [] (const std::size_t i) const { return a_[i] + b_[i]; } private: const array<Type,Size>& a_; const array<Type,Size>& b_; }; template <class Type, std::size_t Size> class array_multiplication { public: array_multiplication (const array<Type,Size>& a, const array<Type,Size>& b) : a_(a), b_(b) {} const Type operator [] (std::size_t i) const { return a_[i] * b_[i]; } private: const array<Type,Size>& a_; const array<Type,Size>& b_; }; template <class Type, std::size_t Size> class array_scalar_multiplication { public: array_scalar_multiplication (const array<Type,Size>& a, const Type& b) : a_(a), b_(b) {} const Type operator [] (std::size_t i) const { return a_[i] * b_; } private: const array<Type,Size>& a_; const Type& b_; }; template <class Type, std::size_t Size> inline array<Type, Size> operator + (const array<Type,Size>& a, const array<Type,Size>& b) { return array<Type,Size> (array_addition<Type,Size>(a,b)); } template <class Type, std::size_t Size> inline array<Type, Size> operator * (const array<Type,Size>& a, const array<Type,Size>& b) { return array<Type,Size> (array_multiplication<Type,Size>(a,b)); } template <class Type, std::size_t Size> inline array<Type, Size> operator * (const array<Type,Size>& a, const Type& b) { return array<Type,Size> (array_scalar_multiplication<Type,Size>(a,b)); } template <class Type, std::size_t Size> inline array<Type, Size> operator * (const Type& a, const array<Type,Size>& b) { return array<Type,Size> (array_scalar_multiplication<Type,Size>(b,a)); } int dummy_array[100][100]; int dummy_array2[100][100]; int main () { array<array<int,100>,100> d(10); array<array<int,100>,100> e(14); for (int i = 0; i < 100; ++i) for (int i2 = 0; i2 < 100; i2++) { dummy_array[i][i2] = 10; dummy_array2[i][i2] = 14; } { boost::progress_timer a; for (int i = 0; i < 1000; i++) d + e; } std::cout << "******" << std::endl; { boost::progress_timer b; for (int i3 = 0; i3 < 1000; i3++) for (int i = 0; i < 100; ++i) for (int i2 = 0; i2 < 100; i2++) { dummy_array[0][0] = dummy_array[i][i2] + dummy_array2[i][i2]; } } return 0; } I get 0.08s for the expression template version and 0.01s for the built-in array version. (release) And 3.00s for the exression template version and 0.05s for the built-in array version. (debug) Thanks. |
||||
|
||||
All times are ET (US)![]() |
Last Thread Next Thread ![]() |
|