The Art of Unit Testing
FrazzledDad writes "'We let the tests we wrote do more harm than good.' That snippet from the preface of Roy Osherove's The Art of Unit Testing with Examples in .NET (AOUT hereafter) is the wrap up of a frank description of a failed project Osherove was part of. The goal of AOUT is teaching you great approaches to unit testing so you won't run into similar failures on your own projects." Keep reading for the rest of FrazzledDad's review.
The Art of Unit Testing with Examples in .NET
author
Roy Osherove
pages
296
publisher
Manning
rating
9/10
reviewer
FrazzledDad
ISBN
1933988274
summary
Soup-to-nuts unit testing with examples in .NET
AOUT is a well-written, concise book walking readers through many different aspects of unit testing. Osherove's book has something for all readers, regardless of their experience with unit testing. While the book's primary focus is .NET, the concepts apply to many different platforms, and Osherove also covers a few Java tools as well.
Osherove has a long history of advocating testing in the .NET space. He's blogged about it extensively, speaks at many international conferences, and leads a large number of Agile and testing classes. He's also the chief architect at TypeMock, an isolation framework that's a tool you may make use of in your testing efforts – and he's very up front about his involvement with that tool when discussing isolation techniques in the book. He does a very good job of not pushing his specific tool and also covers several others, leaving me feeling there wasn't any bias toward his product whatsoever.
AOUT does a number of different things really, really well. First off, it focuses solely on unit testing. Early on Osherove lays out the differences between unit and integration tests, but he quickly moves past that and stays with unit tests the rest of the book. Secondly, Osherove avoids pushing any particular methodology (Test Driven Development, Behavior Driven Development, etc.) and just stays on critical concepts around unit testing.
I particularly appreciated that latter point. While I'm a proponent of *DD, it was nice to read through the book without having to filter out any particular dogma biases. I think that mindset makes this book much more approachable and useful to a broader audience – dive in to unit testing and learn the fundamentals before moving on to the next step.
I also enjoyed that Osherove carries one example project through the entire book. He takes readers through a journey as he builds a log analyzer and uses that application to drive discussion of specific testing techniques. There are other examples used in the book, but they're all specific to certain situations; the brunt of his discussion remains on the one project which helps keep readers focused in the concepts Osherove's laying out.
The book's first two chapters are the obligatory introduction to unit testing frameworks and concepts. Osherove quickly moves through discussions of "good" unit tests, offers up a few paragraphs on TDD, and lays out a few bits around unit test frameworks in general. After that he's straight in to his "Core Techniques" section where he discusses stubs, mocks, and isolation frameworks. The third part, "The Test Code" covers hierarchies and pillars of good testing. The book finishes with "Design and Process" which hits on getting testing solidly integrated into your organization, plus has a great section on trying to deal with testing legacy systems. There are a couple handy appendices covering design issues and tooling.
Osherove uses his "Core Techniques" section to clearly lay out the differences between stubs and mocks, plus he covers using isolation frameworks such as Rhino.Mocks or TypeMock to assist with implementing these concepts. I enjoyed reading this section because too many folks confuse the concepts of stubbing and mocking. They're not interchangeable, and Osherove does a great job emphasizing where you should use stubs and mocks to deal with dependencies and interactions, respectively.
The walkthrough of splitting out a dependency and using a stub is a perfect example of why this book's so valuable: Osherove clearly steps through pulling the dependency out to an interface, then shows you different methods of using a stub for testing via injection by constructors, properties, or method parameters. He's also very clear about the drawbacks of each approach, something I find critical in any design-related discussion – let me know what things might cause me grief later on!
While the discussion on mocking, stubbing, and isolation was informative and well-written, I got the most out of chapters 6 ("Test hierarchies and organization") and 7 ("The pillars of good tests"). The hierarchy discussion in particular caused me to re-think how I've been organizing an evolving suite of Selenium-based UI tests. I was already making use of DRY and refactoring out common functionality into factory and helper methods; however, Osherove's discussion led to me re-evaluating the overall structure, resulting in some careful use of base class and inheritance. His concrete examples of building out a usable test API for your environment also changed how I was handling namespaces and general naming.
If you're in an organization that's new to testing, or if you're trying to deal with getting testing around legacy software, then the last two chapters of the book are must-read sections. Changing cultures inside organizations is never easy, and Osherove shows a number of different tools you can use when trying to drive the adoption of testing in your organizations. My own experience has shown you'll need to use combinations of many of these including finding champions, getting management buy off, and most importantly learning how to deal with the folks who become roadblocks.
The Art of Unit Testing does a lot of things really well. I didn't feel the book did anything poorly, and I happily include it in my list of top software engineering/craftsmanship books I've read. All software developers, regardless of their experience with unit testing, stand to learn something from it.
You can purchase The Art of Unit Testing with Examples in .NET from amazon.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
Osherove has a long history of advocating testing in the .NET space. He's blogged about it extensively, speaks at many international conferences, and leads a large number of Agile and testing classes. He's also the chief architect at TypeMock, an isolation framework that's a tool you may make use of in your testing efforts – and he's very up front about his involvement with that tool when discussing isolation techniques in the book. He does a very good job of not pushing his specific tool and also covers several others, leaving me feeling there wasn't any bias toward his product whatsoever.
AOUT does a number of different things really, really well. First off, it focuses solely on unit testing. Early on Osherove lays out the differences between unit and integration tests, but he quickly moves past that and stays with unit tests the rest of the book. Secondly, Osherove avoids pushing any particular methodology (Test Driven Development, Behavior Driven Development, etc.) and just stays on critical concepts around unit testing.
I particularly appreciated that latter point. While I'm a proponent of *DD, it was nice to read through the book without having to filter out any particular dogma biases. I think that mindset makes this book much more approachable and useful to a broader audience – dive in to unit testing and learn the fundamentals before moving on to the next step.
I also enjoyed that Osherove carries one example project through the entire book. He takes readers through a journey as he builds a log analyzer and uses that application to drive discussion of specific testing techniques. There are other examples used in the book, but they're all specific to certain situations; the brunt of his discussion remains on the one project which helps keep readers focused in the concepts Osherove's laying out.
The book's first two chapters are the obligatory introduction to unit testing frameworks and concepts. Osherove quickly moves through discussions of "good" unit tests, offers up a few paragraphs on TDD, and lays out a few bits around unit test frameworks in general. After that he's straight in to his "Core Techniques" section where he discusses stubs, mocks, and isolation frameworks. The third part, "The Test Code" covers hierarchies and pillars of good testing. The book finishes with "Design and Process" which hits on getting testing solidly integrated into your organization, plus has a great section on trying to deal with testing legacy systems. There are a couple handy appendices covering design issues and tooling.
Osherove uses his "Core Techniques" section to clearly lay out the differences between stubs and mocks, plus he covers using isolation frameworks such as Rhino.Mocks or TypeMock to assist with implementing these concepts. I enjoyed reading this section because too many folks confuse the concepts of stubbing and mocking. They're not interchangeable, and Osherove does a great job emphasizing where you should use stubs and mocks to deal with dependencies and interactions, respectively.
The walkthrough of splitting out a dependency and using a stub is a perfect example of why this book's so valuable: Osherove clearly steps through pulling the dependency out to an interface, then shows you different methods of using a stub for testing via injection by constructors, properties, or method parameters. He's also very clear about the drawbacks of each approach, something I find critical in any design-related discussion – let me know what things might cause me grief later on!
While the discussion on mocking, stubbing, and isolation was informative and well-written, I got the most out of chapters 6 ("Test hierarchies and organization") and 7 ("The pillars of good tests"). The hierarchy discussion in particular caused me to re-think how I've been organizing an evolving suite of Selenium-based UI tests. I was already making use of DRY and refactoring out common functionality into factory and helper methods; however, Osherove's discussion led to me re-evaluating the overall structure, resulting in some careful use of base class and inheritance. His concrete examples of building out a usable test API for your environment also changed how I was handling namespaces and general naming.
If you're in an organization that's new to testing, or if you're trying to deal with getting testing around legacy software, then the last two chapters of the book are must-read sections. Changing cultures inside organizations is never easy, and Osherove shows a number of different tools you can use when trying to drive the adoption of testing in your organizations. My own experience has shown you'll need to use combinations of many of these including finding champions, getting management buy off, and most importantly learning how to deal with the folks who become roadblocks.
The Art of Unit Testing does a lot of things really well. I didn't feel the book did anything poorly, and I happily include it in my list of top software engineering/craftsmanship books I've read. All software developers, regardless of their experience with unit testing, stand to learn something from it.
You can purchase The Art of Unit Testing with Examples in .NET from amazon.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
This will fit nicely besides my msbuild book collecting dust on my desk. Jokes aside, we do tons of unit testing and I have never seen a book solely on unit testing for .NET with TDD, mocking, etc.
I'm stoked!
Tired of my customary (Score:1)
When I first saw the article's title, I thought that this was the UNIT it was referring to. Says a lot about the type of people I hang out with, doesn't it?
Good, inexpensive web hosting
For anyone familiar with the basics of unit testing but struggling to implement it in real world scenarios, I'd strongly recommend xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros.
The idea is not only that automated testing is good, but that testable code is fundamentally better because it needs to be loosely coupled. I still struggle to follow TDD in many scenarios, especially where I'm closely interacting with system APIs, but just reading xUnit Test Patterns has given me tons of ideas that improved my code.
LOAD "SIG",8,1
When I was a young man in the program, they tested the unit by having us march shoeless through 2 miles of uphill, mine-ridden, barbed-wire-laced snow! The unit got tested, and tested HARD! The program didn't allow for no pansy-ass pussy-footers. And did the unit in the program pass its tests? By God it did! You youngsters got it easy just havin to do some stupid vocabulary test to test your unit in the program. Plugging in words. HAH! Try plugging in the gaping hole left by the bark of an exploding tree!
The only part that is an "art" is working out how to successfully isolate the component that you're trying to test. For simple components at lower layers (typically data CRUD) it's not so hard. Once you find you're having to jump through hoops to set up your stubs, it gets harder to "fake" them successfully and becomes a more error prone and time consuming process. It can also be difficult if there's security in the way. The very checks you've put in to prevent security violations now have to be worked around or bypassed for your unit tests. There's also a danger of becoming too confident in your code because it passes the test when run against stub data. You may find there's a bug specific to the interfaces you've stubbed. (For example a bug in a vendor's database driver, or a bug in your data access framework that doesn't show up against your stub).
All of those distracting side issues and complications aside, we are dealing with fundamental engineering principles. Build a component, test a component. Nothing could be simpler, in principle. So it's disappointing when developers get so caught up in the side issues that they resist unit testing. There does come a point where working around obstacles makes unit testing hard and you have to way benefit against cost and ask yourself how realistic the test is. But you don't go into a project assuming every component is too hard to unit test. That's just lazy and self-defeating. It comes down to the simple fact that many programmers aren't very good at breaking down a problem. In industries where their work was more transparent, they wouldn't last long. In software development where your code is abstract and the fruit of your work takes a long time to get to production, bad developers remain.
These posts express my own personal views, not those of my employer
I read this book recently and found it tiring. Much of it reads like a blog, and like many books, the author randomly switches stances. He'll refer to the reader as "the reader", "you", "we", and in the third person. This is the kind of book where it's hard to keep a clear idea of what the author is talking about, because he doesn't have a clear idea of what he's trying to communicate.
When I think of tiring books like this, I cannot avoid always remembering Steve McConnel's Code Complete (first edition; I haven't looked at the second edition yet). Reading that book is like having your autonomy assaulted, because the author constantly tries to get you to accept the things he's claiming, via whatever means necessary, rather than presenting them along with rational arguments, and letting you decide when to apply them. I'm not saying Osherove's book is that bad, just that it has that same unenjoyable aspect that makes it a chore to read and get useful information from.
I recently also read Kent Beck's Test-Driven Development and highly recommend it, if you simply want to learn about unit testing and test-driven development. It's concise and enjoyable to read. Unfortunately it doesn't cover as many details, and I don't have any good alternatives to books like Osherove's (and I've read many at my local large university library).
I'm fairly experienced with unit testing, and I've read several books on the subject. This is by far the best introduction to unit testing I have read. The book, in very practical terms, explains in 300 pages that which took me about five years to learn the hard way. I think this book also has a lot of value for unit testers that got their start a decade or more ago but haven't kept up with recent trends.
So she uses your unit to test the magnification level of newer electron microscopes? Have she come across one yet that has the resolution to find yours?
I've read several unit testing books recently, and another I found somewhat useful is Michael Feathers' Working Effectively with Legacy Code. It has all sorts of techniques for testing legacy code, i.e. code that wasn't designed for testability, and which you want to make as few modifications to. So he gets into techniques like putting a local header file to replace the normal one for some class used by the code, so that you can write a replacement class (mock) that behaves in a way that better exercises the code. Unfortunately Feathers' book is also somewhat tiring to read, due to a verbose writing style and rough editing, but I don't know anything better.
I work on distributed real-time software (financial industry) and can tell you that unit tests for components I write are either
1. trivial to write, therefore useless
2. impossible to write, therefore useless
I find full logging and reliable time synchronization both easier to implement and more useful in tracking bugs and / or design errors in environment I deal with than unit testing.
tl;dr - check return values, catch exceptions and dump them in your logs (and use state machines so you know where exactly you were, and so on...)
"Blah blah blah." - [citation needed]
This isn't (solely) about error handling. It's about logical testing, which can be as nasty as a technical error. For instance, replacing your real database with a fake one, then run through your business logic modules and make sure the right data is getting passed back, and that any calls you make to change that data get reflected in the right updates to the database. The same techniques can be used to find out what your code does if it tries to talk to the database and the database is out to lunch.
What you're describing is more integration testing, where you test the whole thing from front-end to lowest-level code, probably with the help of a dedicated tester / QA analyst. This is also good, but these other techniques are often handy for isolating problems quickly as well as being able to improve one piece and see immediately what effects it has on other pieces.
I am officially gone from
Maybe you're on the wrong thread? It's a review of a book about unit testing, not error handling. The two are only moderately related, as far as programming concerns go.
-- 77IM
Student: Is it true that the foundation of the universe is paradox?
Master: Well, yes and no.
Fortunately, modern concepts like exceptions have eliminated the need for steps 2 through 5. It is very annoying to go back to old code and see:
... And so on, indented to the 500th column...
return_value = DoStep1();
if (return_value != success) then handle_error(return_Value);
return_value = DoStep2();
if (return_value != success) then handle_error(return_Value);
return_value = DoStep3();
if (return_value != success) then handle_error(return_Value);
.
.
.
or worse:
return_value = DoStep1();
if (return_value == success)
{
return_value = DoStep2()
if (return_Value == success)
}
instead of:
DoStep1();
DoStep2();
DoStep3();
upon failure, handle_error.
*Detecting* the problem isn't hard. What's hard is *handling* it -- and there was nothing about that on your list. Hint: calling abort(2) is not always acceptable.
Rule #1 of all testing: The purpose of testing is not to prove that the code works: the purpose of testing is to *try to break* the program.
(A good tester is Evil: extremes of values, try to get it to divide by 0 etc.)
I was at Dev Days in Toronto a few months ago, and one of the speakers brought up a very good point relating to different software engineering methodologies. He said that despite all the literature written on them, and the huge amount of money involved, there has been very few good studies on the effectiveness of various techniques. He went on to challenge the effectiveness of unit testing and 'agile development.' The only methodology he had found studies to demonstrate significant effectiveness was peer code review.
This brings me to my question. Does this book say anything concrete with citations to back it up, or is it all the opinion of one person?
Error handing and reporting is very complex. This is mostly due to the complexities involved with different parts of a system interacting with each other.
Windows GetErrorInfo call will return NULL on the second call, despite no other COM calls made in-between. This took a while to understand what was happening.
Do you check that an IErrorInfo object is valid for the method you just called by using ISupportsErrorInfo? Do you check to see if this is a C#/.NET System.Exception (_Exception) and then use ToString to get back the nice stack trace?
Does the Windows API call return a HRESULT, an NT error code, a registry API error code, a BOOL with a corresponding GetLastError call to get the details or something else? Are you checking errno on all C API calls? Or the appropriate error checking call for the API you are working with? Do you realise that some Windows APIs have different return code behaviour on Win9x and NT+ (e.g. the GDI calls)?
Do you guard all your COM calls written in C++ to ensure that a C++ exception does not leak outside the COM boundary? Do you report said exception as a HRESULT and IErrorInfo that lets you track down the problem?
Do you ensure that an exception is not thrown from outside a destructor? A thread function? A Windows/GTK/Qt/... event handler? Across language boundaries?
How do you present an error to the user? Are you showing the exception message (e.g. displaying E_UNEXPECTED as "Catastrophic failure")?
It is not always possible to write perfect code. Developers will forget to check for access permissions to a file before writing to it, or just ignore any errors (did you know that std::ofstream will not report an error if the file it is writing to is read-only (at least with the Microsoft C++ implementation and Windows, without any fancy std::ios flags?)).
Have you ever dealt with infinite recursions that involve 3 or more functions? Do your COM/DBus/... calls check for/handle network failures? Do you report these in a friendly way to the user? Do you try to recover/reconnect in this case?
Knowing exactly what a system call does is also impossible, unless you have access to the source code for that particular configuration. The MSDN documentation is not reliable for Windows APIs, as it leaves a lot of the important stuff out. The POSIX documentation only covers the important/most common error cases.
Somebody likes their Anime a bit too much.
You seem to think that "art" refers to something that is fundamentally mysterious. A lot of art is, but that's not an intrinsic feature. The word itself has a lot different meanings. Here are some of the most fundamental, from the Oxford English Dictionary.
1. Skill in doing something, esp. as the result of knowledge or practice.
2. Skill in the practical application of the principles of a particular field of knowledge or learning; technical skill. Ob
3. As a count noun.
a. A practical application of knowledge; (hence) something which can be achieved or understood by the employment of skill and knowledge...
b. A practical pursuit or trade of a skilled nature, a craft; an activity that can be achieved or mastered by the application of specialist skills...
c. A company of craftsmen; a guild.
4. With modifying word or words denoting skill in a particular craft, profession, or other sphere of activity.
5. An acquired ability of any kind; a skill at doing a specified thing, typically acquired through study and practice; a knack. Freq. in the art of —.
Before you can offer an informed opinion as to what is and is not engineering, you need to read something by Henry Petroski. He defines it as "the art of rearranging the materials and forces of nature".
Check the permissions when you access a resource.
Careful, you can easily have a race condition there. Say, you're trying to open a file. You check for permissions before doing so, and find out that everything is fine. Meanwhile, another process in the system does `chmod a-r` on the file - and your following open() call fails, even though the security check just succeeded.
Microsoft's version of .ORG - it's still in the first of the 3 E's.
Fortunately, modern concepts like exceptions have eliminated the need for steps 2 through 5.
They have minimized the need for steps 2 through 5, not eliminated it. Exception handling is expensive, and you should only rely on catching exceptions for errors that are not supposed to happen often. If a pointer is not supposed to be null, you can rely on catching an exception. If the pointer could be null and you're supposed to create a new object in case it is, you check it.
Basically, if you're throwing an exception, it should be for something unexpected that shouldn't have happened, and now it requires you to ask the user "wtf did you do? I'm getting garbage in, so instead of giving you garbage out, here's the error." For anything that could be a normal state of the program, handle it yourself.
(Displaying an error and terminating the program is almost always the wrong answer.)
It might not be the best answer, but it's better than ignoring the error without fixing the cause, or trying to handle the error transparently, and failing. If in doubt, terminate with a meaningul error. That way if the error condition is ever triggered (hopefully in testing), it's easy to find and diagnose.
Just because you're paranoid doesn't mean there isn't an invisible demon about to eat your face
[waves hand in front of face]
Que Deus te de em dobro o que me desejas
[May God give you double that which you wish for me]
It sort of sounds familiar before auto-save become a standard feature on pretty much all word-processors. Failing (relatively) gracefully and notifying the user is better than continuing along with corrupt data - even in transaction processing systems. Of course, by the time something gets to production, it (ideally) shouldn't even encounter it's error states.
Just because you're paranoid doesn't mean there isn't an invisible demon about to eat your face
Actually, I despise programmers that depend on exceptions. If you are trying to work on any kind of hardened system, like a real-time system, or a system that "just has to work", or a system that must work in fixed memory, exceptions are nightmares. You have to prove that every single exception either can't happen or can be safely handled. Even "safe" handling can be a challenge. For instance, if an error occurs, the control system must keep running, otherwise more errors occur. In some contingencies, the software must just "keep handling errors". It is amazing how many functions can't handle a constant stream of errors without leaking memory, pausing for long periods of time, or pausing for operator input when no operator is present.
One project involved complex mechanical automation. The system performance was defined solely by the rate exceptions to the normal process occured. It was vital to have software that just works. If the mechanical exceptions caused exceptions in the computer software, then everything stopped working. It was impossible to keep track of all the complexities involving all the exceptions.
The only solution was simple software that had very few error cases, and each error case clearly exposed the error handling. As such, all error cases could be checked. Curiously, this was the same system that worked much better when ported from .NET to C. For low-level code, C is a much better language, no matter how trendy .NET is.
Interesting that you should mention Kent Beck's book, as I too have read it recently and found it to be the shittiest pile of steaming turd at I've ever seen put into book form. It was *SO* slow going, so condescending, and it was so sorely lacking in the way of cohesive rational arguments that if I hadn't been sold TDD through other means, I would have abandoned the concept altogether. It might be okay as an introduction to a complete novice programmer, but if you've had any experience in the industry at all, I'd recommend avoiding it unless you either want something to put you to sleep, or you're a sucker for punishment.
you only pay the price when the exception is triggered...
That's why I said that the place to use it is when you don't expect the exception to be triggered often. However, I've seen plenty of people use exceptions as a state machine for what they're coding, and if you're doing that, return codes values are the way to go.
All your points are good, but I think they apply regardless of whether or not you use exceptions. If you write the tedious logic that I gave in my example, you still have the same code to handle the return values. For microcontroller coding, just think of exceptions as an automatic if-then-else that is placed around every function call. If the function call succeeds the check is free. If the function call fails, the check is expensive. It's quite a nice trade-off.
Funny note: I work for a medical equipment manufacturer, who uses vxWorks and C++. They use exceptions in their coding, and most of their memory allocations are fixed due to memory constraints.
If the mechanical exceptions caused exceptions in the computer software
It's interesting you say that. In our app, mechanical exceptions are the one place where exceptions are not permitted to cascade upward. They are exceptions, in that they are very rare and we don't want to write the tedious code to check return values every time. Exceptions are just an easier way to handle the return_value pattern I showed in my example. It really doesn't change very much.
I have seen some books on using Java on microcontrollers. I have never done it, but I would curious to see how they approach it since Java has checked exceptions.
I think many are getting caught up in terminology and forgetting (or perhaps they never knew) that the overall general purpose of any testing is to eliminate assumptions. Do the requirements really reflect what the customer wants? Does the system really meet the specs? Does component X really perform its job? Or are these things just assumed to be true? Testing gives one the ability to find out.
Now the decision of what to test and what to ignore is an important one, and ultimately it comes down to recognizing one's assumptions. What is one willing to assume, and what must they really know for certain?
and unit specification early on in my career with a documentation technique which let me specify the order of as well as the limits of the API (whether human or systemic components were involved.)
My success and income over the years was derived from the work doe in 1983-84, printer in Computer Language Magazine in 1990 and released into the wild in 2007.
Check out http://media.libsyn.com/media/msb/msb-0195_Rovira_Diagrams_PDF_Test.pdf
MSBPodcast.com The opinions expressed here are my own. If you don't like 'em... Think up your own stuff.