Sign in to follow this  

Unit Tests and Test-Driven Development

This topic is 3582 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm looking for a resource (book, website, etc) about the two topics of unit tests and test-driven development. I know the basic idea of each from the great wikipedia, but I have a hard time understanding how it could be applied to any real software system. In the last several projects I've worked on, it seems like all the systems involved were far too complicated for test cases to do more than cover the most trivial of cases. For example, I implemented the original Lisp from John McCarthy's paper "Recursive Functions of Symbolic Expressions and their Computation by Machine" in C++, and it involved writing a memory manager and several inter-recursive functions. In order to test any part of the system, the whole thing had to be tested, and there are no clear 'boundary conditions' that make themselves obvious as test points. The tests I eventually constructed were just random examples in the paper of programs and what they should evaluate to, and each test ended up being something like 50 lines of code when the whole rest of the program was around 500 lines. It just doesn't seem reasonable to have tests that test so little and yet are so big, so I have the strong feeling that I'm doing something significantly incorrect and/or I just don't get it. Thus, I'm looking for some resources that might help me fix this problem in future projects so they can be better tested.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Try Object Mentor's published articles, especially the Craftsman series.
It's probably not good that the main thing that sticks in my mind as I read the code in the Craftsman articles is "Eww! Not Thread Safe!"

Share this post


Link to post
Share on other sites
Quote:
Original post by Extrarius
Quote:
Original post by SiCrane
Try Object Mentor's published articles, especially the Craftsman series.
It's probably not good that the main thing that sticks in my mind as I read the code in the Craftsman articles is "Eww! Not Thread Safe!"


Thread safety is overrated.

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
Of course not. But a blanket statement does not an argument make. Especially considering that we now program in the era of multiple cores.


And the solution to that is languages with good concurrent programming models, not "OMG make stuff thread safe with locks!".

Share this post


Link to post
Share on other sites
Quote:
Original post by truthsayer
And the solution to that is languages with good concurrent programming models, not "OMG make stuff thread safe with locks!".

You seem to have a very narrow idea what thread safety means. A function can be thread safe without locks.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by truthsayer
And the solution to that is languages with good concurrent programming models, not "OMG make stuff thread safe with locks!".

You seem to have a very narrow idea what thread safety means. A function can be thread safe without locks.


Okay then, if you want to be fussy, the problem isn't just locks but anything using a shared memory model. Swappings locks for DCAS doesn't make it any better.

Share this post


Link to post
Share on other sites

// thread safe
int add(int a, int b) {
return a + b;
}

// not thread safe
int add(int a, int b) {
static int accumulator;
accumulator = a;
accumulator += b;
return accumulator;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane

// thread safe
int add(int a, int b) {
return a + b;
}


There's no memory shared there.

Now try writing an entire program this way. It's not really possible in traditional languages built around the shared memory concurrency model, sadly.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by truthsayer
There's no memory shared there.

Exactly my point.


So you're talking about a completely uninteresting and trivial case. Congratulations must be in order.

Share this post


Link to post
Share on other sites
Quote:
Original post by truthsayer
Quote:
Original post by SiCrane

// thread safe
int add(int a, int b) {
return a + b;
}


There's no memory shared there.

Now try writing an entire program this way. It's not really possible in traditional languages built around the shared memory concurrency model, sadly.


lol, get a clue.

Share this post


Link to post
Share on other sites
Quote:
Original post by truthsayer
[...]There's no memory shared there.[...]
That is exactly my objection to the examples in the articles - they introduce shared memory for no reason, as far as I can tell. Perhaps it's a java thing (as I'm not familiar with all the details of java), but in C++ you could use exactly the same design with exactly the same variables but make it completely thread safe by passing around references to local objects instead of using global ones.

Share this post


Link to post
Share on other sites
Quote:
Original post by truthsayer
Quote:
Original post by silvermace
lol, get a clue.


You must be that scholar I was looking for.

Care to explain yourself, or are you afraid your ignorance will shine through?

Oh man, talk about unfriendliness. SiCrane is one of the more knowledgeable people I know on this forum and I know for a fact that he has no intentions but to help others. Dude, you seriously need to chill out.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ashkan
Quote:
Original post by truthsayer
Quote:
Original post by silvermace
lol, get a clue.


You must be that scholar I was looking for.

Care to explain yourself, or are you afraid your ignorance will shine through?

Oh man, talk about unfriendliness. SiCrane is one of the more knowledgeable people I know on this forum and I know for a fact that he has no intentions but to help others. Dude, you seriously need to chill out.

Nah, not anymore he doesn't... cus he's banned! Wee!

As far as the object mentor articles go:
Thread safety was not a primary concern when writing them, as threading was not not on the docket as far as concerns go for implementation of the problem they are approaching.

Share this post


Link to post
Share on other sites
While I appreciate the defense, he wasn't quoting me.

In any case, threading does get mentioned eventually in them as a something you need to watch for in the unit tests when you actually need multiple threads. There's a character in the series who actually gets quite snooty about it near the middle-endish of the current batch.

Share this post


Link to post
Share on other sites
I've read up to Craftsman 31, and as far as I can tell, my real problem is that, at heart, I work like I a hacker - that is, I start with a vague idea of what I want to do, and the requirements are made up as I notice the code doesn't do something I want it to do.

For example, I started the Lisp project by writing the two core objects of Lisp - the Atom and the Pair (cons cell). Since Pairs store pointers, I needed to make some way to allocate new objects, so I wrote MakeAtom(string) and MakePair(LispObject, LispObject). I needed some way to delete objects at the end of the program, so I made the two functions add new objects to a list. Then I wrote Evaulate and noticed that it can potentially create a lot of objects with all it's recursion, so I decided to write a stack-based memory manager that could clean up as calls to Evaluate returned. Then, I started testing Evaluate and did TONS of debugging based on a few example programs in the paper it was all based on. Too much of the debugging was of the tests themselves, and I noticed I was having a hard time testing it because I would often accidentally construct lists incorrectly, so I created a class that made it easier to construct lists (and spent a bit of time debugging a case where the copy constructor was called when I expected a conversion and regular constructor). Then I remebered that I probably wanted to implement a 'Read' function as well, that could convert a string into one or more LispObject in order to construct a REPL, and I did that, and then testing became trivial because I could write the tests as text instead of convoluted series of MakePair and MakeAtom. Finally, I spent a ton of time debugging trying to figure out why evaluate would occasionally recurse indefinitely, and found that an error condition wasn't being handled correctly, and that it should basically throw an exception to get out of the recursion. I still haven't finished the project, but I did the whole "make it work" thing and now it just needs about ten times as many man-hours spent on refactoring as I've already spent creating it.

It's rather obvious that in order to use unit tests and test-driven design properly, you really have to know what (most of) the requirements are up front, but I have no idea how to break a large idea into small, testable requirements. I'm so used to "design by accident"(I think that is the proper term for 'not really designing') that I just don't see the trees that make up the forest =-/

I have a specific hobby project in mind that I know I'm going to need this kind of thing for (because it being absolutely correct is vital for it to be worth anything). What is the best way to tackle this mental block?

Share this post


Link to post
Share on other sites
Quote:
Original post by Extrarius
I have a specific hobby project in mind that I know I'm going to need this kind of thing for (because it being absolutely correct is vital for it to be worth anything). What is the best way to tackle this mental block?


I found myself in this situation a while ago. I found a quite simple solution: I decided not to code anything. Instead, I spent 4€ on a small blue notebook and went outside in a park for a few hours every time I could, so I could think about the program. I wrote the pseudocode, complexities and proofs of correctness of my algorithms, looked for the shortest and smallest designs that could achieve what I wanted, and tried to isolate a few typical use cases to see if improvement was possible. Then, I wrote the corresponding program in a single afternoon, and it worked on the first try (aside from a few typos that messed things up, and situations where I translated badly the concepts in the notebook, but these came up within the first hour of testing).

Share this post


Link to post
Share on other sites
Quote:
Original post by Extrarius
It's rather obvious that in order to use unit tests and test-driven design properly, you really have to know what (most of) the requirements are up front, but I have no idea how to break a large idea into small, testable requirements. I'm so used to "design by accident"(I think that is the proper term for 'not really designing') that I just don't see the trees that make up the forest =-/


I had a much longer post, and then a server error killed it. Dammit.

Anyway, this is not true at all. The entire point of TDD is to guide the development process. You *have* to change how you write code, that's what drove you to TDD in the first place, your old of writing code was leading to you writing bugs. You need to rethink your development process to emphasize pre- and post-conditions so that you can test those conditions.

The key is that, once a test is written, it doesn't go away. You can use tests as reminders of code that you need to write, or tests that you need to write but haven't figured out how to do it yet. You can use tests as reminders of how to *use* code. You can use tests to show where big changes in code break old functionallity. It sticks around and still serves a purpose, even after you think the thing it tested is long since solved.

Here is an article I wrote on TDD a while ago. It's short, it's meant to be only one page. I use it for evangelism in the office.

Share this post


Link to post
Share on other sites
The ideas for my projects work kind of like a wave function - as long as it exists in it's natural state, there are a vast number of possibilities that form a nebulous cloud, and it's not until I'm actually coding a feature that the the idea collapses into a single value that is the implementation I actually go with.

The problem is that I don't know how to describe the nebulous entity in any way that is testable, and once I get to the collapsed version, "design by accident" is already taking place.

On top of that, since the project in my mind is a comples type of game, I have a hard time visualizing how it could be easily broken down into automated tests even if I did have the whole thing worked out in a concrete system, because the number of possible states is practically unlimited and the number of actions in each states is very large. How can I "design-by-testing" a specialized scripting engine when the small pieces don't do anything special but the system as a whole does something unique? I can certainly test the foundation to see that it works "as advertised" but that still doesn't show the higher levels work as they must.

Share this post


Link to post
Share on other sites

This topic is 3582 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this