August 28, 2006The danger of mock objectsSomebody recently asked how he could test a void method that is supposed to close a connection. I responded:
Somebody else said:
The problem with this answer is that if you upgrade your JDBC driver and that in the new version, close() is broken, your test will keep passing but your application will fail. Mock objects can give you a deceptive sense of confidence, and that's why you should avoid them unless there is really no alternative. Posted by cedric at August 28, 2006 07:19 AM Comments
Generally speaking I cannot agree with you as I "intensively" use mock objects for many years now - and with success as far as I can say. Erik Hi Cedric, IMHO both approaches are valid. In your Unit Test you will/should use a mock object, because you want to test YOUR code/class/method. A Unit Test works under the assumption "everything else" work. For an integration test you will test with a real JDBC connection. An integration test works under the assumption "all pieces work - now let's see if they work together". Unit tests DO NOT verify that your application works. In this case, your unit test is verifying that Close() is called. That's it. One unit test, one assertion. If you want to verify that Close() works as intended, then write a different unit test for that. The mock object answer is certainly the correct one - Your three-step test is really more of an integration test than a unit test. In your case, you're also testing if the connection works properly and responds properly when operations are attempted when closed. That's a lot more than what the method in question is actually doing. By testing too much, your unit tests lose some value - if your test fails, where's the issue? Is it in the original method, or in the connection provider? the data source? If I change the original method and the test still passes, is that because the connection provider is smart enough to fix my bug? No, I see value in integration testing, but make sure you know what you're testing and call it what it is. Posted by: Philip at August 28, 2006 08:19 AMIsn't this exactly the reason why you would use mocks in a unit test? Wouldn't this still indicate that the code that you wrote is still solid, and that the problem must be elsewhere? As long as you still have acceptence tests that would catch the bad driver problem, you would still be in a good scenario to diagnose the problem. Posted by: Dustin at August 28, 2006 08:22 AMMy own thoughts at ( http://blogs.concedere.net:8080/blog/discipline/software+engineering/?permalink=The-Mock-Objects-Are-Coming.html ) but I'm inclined to agree with Cedric on this point. Posted by: ocean at August 28, 2006 09:25 AMWhen you start saying things such as "mock objects are good for unit tests but not for integration tests", you are beginning to lose the big picture, which is: you are testing so your application will work. Whether you use a mock object or not, you write a unit test or an integration test, every single line of test code you write should work toward that goal: make sure your application works. This particular case illustrates a very simple point: mock objects should only be used when the object under test is hard to materialize in a test. It's clearly not the case here since previous tests were most certainly running on a working Connection, so you might as well reuse that connection to test close(). While I can conceptually understand the purpose of separating unit tests from integration tests, I think that there are many situations in which this distinction provides less "bang for your buck" than just focusing on the integration tests. Unless the mock/real or unit/integration components can be switched at runtime by an easy configuration switch, but this doesn't seem possible with the mock-specific code that needs to be written to ensure that your mocks were interacted with in the expected manner. I typically work on teams and projects where I spend a lot of energy trying to convince people to write any form of tests at all. TDD and mock objects are a pipe dream when I can't even sell JUnit as a "good thing". Posted by: earl at August 28, 2006 10:22 AM"Mock objects allow you to verify that your code actually does what you think it does..." well, IMHO unit testing is there to verify that your code actually does what you think it does, mock objects actually verify that your code does it in a very specific way. These are two different things. In the case of the connection, you actually test that the implementation should call the close() method... but it doesn't mean that it's actually doing what you want it to do (which is of course closing the connection). Posted by: Uri at August 28, 2006 10:47 AMIt seems that I work in a different environment than some of you do. My every day bread is earned writing code that uses other subsystems that are not yet built or are being built or are rather complex/expensive to put up to work. In such an environment I'm pretty happy to get a fast feedback whether the code I wrote does what I intended to (hoping I call the right method of the foreign subsystem and that this method is up to its promises) and whether a change I made doesn't break anything "obvious" in the code (as tested by the unit tests). Erik This isn't the real danger of mocks, Cedric... this is exactly the scenario mocks are called for. You have a few tests that test your understanding of the API (e.g. that close() works correctly), then everywhere else you test that you use your understanding correctly. The real danger of mocks is when you do NOT test your understanding. It is very easy to use mocks in such a way that you codify an erroneous assumption (e.g. that database connections do not _need_ to be closed, because they are in a pool, and closing it would make the pool re-open it). One thing about mocks is that they tend to push people in particular design directions. e.g. I found myself being pushed towards an IoC approach because I got tired of doing mock connection setup and teardown; I ended up having the connection passed in, ala IoC. Mocks tend to show up violations of the Single Responsibility Principle - if you ask yourself "why do I need to mock this?", the answer is you probably don't - you shouldn't be doing that in that class. Posted by: Robert at August 28, 2006 02:44 PM"The problem with this answer is that if you upgrade your JDBC driver and that in the new version, close() is broken, your test will keep passing but your application will fail." And that's okay for a single unit test. Unit tests that run as part of every build are not going to excercise the entire system. By this logic, if you really want to make sure that this unit test matches production, then the developers are going to need a running instance of the database, the same version that's in production, preferably in the same fault-tolerant configuration, with the identical production JDBC driver, configured with the exact same settings. Otherwise, the unit test might pass but the application will fail. Me, I'd prefer to make it possible to say run a set of tests that verifies the code itself without any external dependencies. The test you outline has value as well, but it should be logically grouped into it's own set of tests. A lot of people have called it an "integration test", but I don't like the binary notion that's developed. There's a hierarchy both in value of the test and the difficulty in automating the test. There are value in both sorts of tests. If your version breaks, then you have to dig through the code (or write the mock version of the test!) to verify that your code is indeed calling close(), and start digging through vendor errata to figure out what might have changed. The mock version tells you your code is doing the right thing, so if your app just broke in a test environment, you can start looking at vendor code right away, and prove to yourself the problem is theirs. "Deceptive sense of confidence" is a misleading way to put it. Mock objects should give you a certain type of confidence, and more complex integration-oriented tests should give you a different type of confidence. And by constantly measuring the value of the type of test by its difficulty in implementation, fragility, or diagnostic utility, you can find the balance of both that works for you. This is something I have been saying internally at my company for a while now. At first they looked really cool but after a while of using them I found that I really use mocks very little in practice. My reasons aren't necessarily because they give me a false sense of security but they just seem to be a pain more than anything else for me. I have used to them to test things like servlet filters and such so that they behave the way they should behave given specific data from the filter. For example, if certain configuration data is not present I want to make sure they'll fail a certain way and so on. I used to sometimes use them for implementing large interfaces that I didn't want to code dummy methods for but Eclipse and other IDEs pretty much made that a non-starter (i.e. implement methods wizards). In reality, though, the interface for them is a pain to remember and it always seems like what I need it to do is not so simple to do. It's usually just easier to have a dummy implementation that I extend and then override the methods for that particular test. As always, for some things and some people mocks may come in really really handy but for my needs at least I go to them very little these days. -Mike Posted by: Mike Bosch at August 28, 2006 03:04 PMI agree with several others; mock objects are perfectly correct here, for unit testing. No amount of unit testing can prove that your application actually works. Integration testing and beyond get you closer to that goal (ultimately, there's no way to "prove" that an application actually works). In any event, Mock objects are great for writing truly localized, independent unit tests. Tests that you run every few minutes. This builds the first layer of confidence before proceding up to slower, more complex integration tests that you run less often. Posted by: Howard Lewis Ship at August 28, 2006 04:33 PMHi See my response at: http://butunclebob.com/ArticleS.UncleBob.TheDangerOfMockObjects Posted by: Robert C. Martin at September 1, 2006 02:54 PMI also have to say that the Mock is perfect here, for this "unit" test, and disagree with those saying unit tests aren't important, etc. I agree that the "integration" tests are indispensable, but the unit tests should still be witten as you write the code, and should isolate the "unit" under test. It is not losing sight of the bigger picture, to write pure "unit" tests - as long as you don't skip the whole integration testing thing, that is! Posted by: Mike Dunbar at September 8, 2006 06:14 AMI also have to say that the Mock is perfect here, for this "unit" test, and disagree with those saying unit tests aren't important, etc. I agree that the "integration" tests are indispensable, but the unit tests should still be witten as you write the code, and should isolate the "unit" under test. It is not losing sight of the bigger picture, to write pure "unit" tests - as long as you don't skip the whole integration testing thing, that is! Posted by: Mike Dunbar at September 8, 2006 06:14 AMI'm not an expert on TDD, but the little experience that I have tells me that I will be running tests about a zillion times. For only one test, for instance, I might fail it 10 times before the final code is done. So, if each run earns me 1 second, that's well worth the effort. My DB cannot be accessced in less than 3s (using hibernate, HSQLDB takes 3s, MYSQL 4s) Posted by: Daniel at October 11, 2006 08:15 AMI'm not an expert on TDD, but the little experience that I have tells me that I will be running tests about a zillion times. For only one test, for instance, I might fail it 10 times before the final code is done. So, if each run earns me 1 second, that's well worth the effort. My DB cannot be accessced in less than 3s (using hibernate, HSQLDB takes 3s, MYSQL 4s) Posted by: Daniel at October 11, 2006 08:16 AMI'm not an expert on TDD, but the little experience that I have tells me that I will be running tests about a zillion times. For only one test, for instance, I might fail it 10 times before the final code is done. So, if each run earns me 1 second, that's well worth the effort. My DB cannot be accessced in less than 3s (using hibernate, HSQLDB takes 3s, MYSQL 4s) Posted by: Daniel at October 11, 2006 08:17 AMPost a comment
|