dallaway.com - Writing - JUnit mailing list |
The JUnit mailing list at Yahoo! occasionally carries discussions on database unit testing. On this page I've collected together some of the threads that I've found useful.
See also: my notes on unit testing databases.
Updated: $Revision: 1.2 $ $Date: 2003/08/04 08:13:09 $ $Author: richard $
26 - 31 Jul 2001: Options for testing
31 Aug - 4 Sep 2001: More autocommit discussion, using JTA/JTS
25 Sep 2001: JDBC testing with mock objects
From: Simon Evans [mailto:sevans@...] Sent: 26 July 2001 19:29 To: junit@yahoogroups.com Subject: [junit] testing database operation i am trying to test objects that use the database. in order to leave the database in the same state after the test, one test method has to do a store, load and delete. is this normal? or is there a way to test each method (store, load, delete) separately, maybe using the setUp() and tearDown() methods? is there a standard pattern for this? also the assert methods do not throw anything...do they throw a RuntimeException or somethnig when they fail? because if i want to do some clean up in a certain test method using a try-catch-finally block, do i have to catch a specific runtime exception? or should all cleanup for all methods be done in the tearDown() method? thanks simon
Date: Fri, 27 Jul 2001 09:00:48 +0100 From: Neil SwinglerSubject: RE: testing database operation The easiest way to clean up the database is to ensure nothing ever gets commited. I aquire my connection in setUp (no autoCommit) and rollback in tearDown().
Date: Fri, 27 Jul 2001 08:52:45 +0100 From: "David Roussel"Subject: RE: testing database operation I use setUp() to establish a DB connection, log the test user into the database. My test methods create, manipulate and assert on the data layer objects. I then use the tearDown() method to delete all objects added. If fact sometimes I also put clean up code in setUp() just case the last test failed. If you want to clean up a after a RunTimeException try use try/finally block... try { assert( thisMightFail ); } finally { doCleanUp(); }
Date: Fri, 27 Jul 2001 14:54:48 -0400 From: "Mathieu Gervais"Subject: Re: testing database operation >This technique, however, is dependant on your locking model. I think you are right, altought I'm not expert in "locking models". Another important point is that you can have code that insist on commiting, for example an entity EJB that you test in the real container environment. Or code that do multi-transaction, so need to commit in between. In that case, you can not do the real neat trick of autoCommit false. I once dreamed a system where every affected row would be rolled back. I investigated many options : Glossary: -"stable data" means data you no is ok and that should be the baseline for the tests, i.e. before the tests are runned, the DB contains that baseline stable data. Note that if your tables are all emtpy (i.e. no baseline data) before runnning the tests, everything is much simpler, you just do a truncate/delete on all tables after every test and you don't need to read on :) Option 1 - Data refresh: refresh data after/before every test. ...from a copy of the baseline data somewhere. Really impossible due to performance. If you think not, let me know how. Option 2 - Data Re-initialize: every test case clean up it's data in tearDown() Pointed out by David Roussel also. That's one of the strategy we use. Works well but you got to be carefull writing your cleanup code. Impossible to use if you are changing stable data rows: only work if just just add rows. Option 3 - use mock objects to fake the DB. Dicussed in the mailing list archives. When you want to test DB code, (SQL, transactions, etc), does not really apply since you fake the part you want to test. Only applies if the actual DB is outside the "unit" under test, which isnot always the case. Option 4 - Data Reset: reverse the changes via some generic black-magic after every test. The dream! Fast, efficient, generic! :) I've spent some time trying to find a neat DB trick do to that one. Never really got the time to find a complete solution and implement it. 4a) Journalling Use some sort of journalling provided by the database to replay in backward direction all the changes made. Is this even possible/supported? 4b) DB tricks, triggers and al. You need a DB that is a copy of your baseline stable data. (Might be in the same DB, but with table names prefixed with 'STBL'(stable)). (You could use 'create table like' to ease making baseline table with the same structure.) Every table as a flag column name BASELINE to keep track of what happens to the line. For your baseline data, set the flag to 'Y'(es). Default on insert for this column in 'N' (New, Not part of baseline). A trigger on update changes the flag to 'U'(updated). A trigger on delete (only effective on a row marked 'Y' or 'U') marks the corresponding row in the baseline copy with 'D'(deleted) After a test: -drop all lines with 'N', -if a line is U, refresh it from the baseline copy. (resetting the flag to 'Y'). -Add all the rows marked 'D' in the baseline copy to the working copy. Daily or weekly, refresh from scratch your working copy from the baseline copy. Do this also if a test case fail and it should really worked and you suspects the code that revert changes to have somehow failed. Beware of autoincrement columns, they will cause re-adding a deleted row to break the system because the column(possibly a key) will change. Any trigger used in the system needs to be analysed to see if it would work with this system (e.g. a trigger that sets a column to the current time will make re-adding a delete row fail, i.e. the time will have changed. This might be ok or not). Special treatment should be done for views also. (Don't run the reversing code on them) (You might also have to drop the integrity constraints while doing this if you don't want to have to do it in the right order respecting integrity constraints). Option 5 - Data Roll forward: don't do anything! Just start the next test without cleaning the junk of the previous one. Might work in some case (i.e. obvious read-only DB case) but is not really a "solution". Option 6 - Auto commit = false ... ...and rollback in tearDown(). Pointed out by Neil Swingler also. Very good solution if applicable (see top of this email for reasons why it would not always apply). Your code should allow this, and your DB also ("locking model"), see email below from David Roussel. If you ever implement 4b, or have any comments or solution, please let me know! :) mathieu
Date: Tue, 31 Jul 2001 09:35:23 +0100 From: Richard Dallaway Subject: Re: testing database operation Mathieu - thanks for putting together that list of options. I think there's some good stuff in there to think about. >Option 1 - Data refresh: refresh data after/before every test. >...from a copy of the baseline data somewhere. >Really impossible due to performance. If you think not, let me know how. If... - you need a lot of data for unit tests (and some codes does); or - your DB machine is on the wrong side of the planet; or - your DB machine/DB product is under-powered; or - you have so many DB tests that you've reached a limit... ...then yes, performance is an issue with this approach. For an ever-growing project it's something you're going to run into sooner or later, I guess. On the other hand, there are some things you can do to make it workable. For example: - if a subsection of tests only touch a couple of tables, then you don't need to wipe and recreate the whole database for those tests. I tend to write helper methods to set up database scenarios and call them as required (rather than touch every table every time in setUp and tearDown). - how much data do you need for unit tests? If you craft your tests carefully, you may be able to get away with just a hand full of records. - as you point out, truncate is very fast for getting rid of data. And for some tests, that's all you need to do. - if your tests are all in one test class, then it's going to be a pain to run that class. You may get better mileage from option 1 if you can break your testing into a number of smaller classes. During a coding frenzy you can run the test class covering just the area you're working on... and run all tests a little less frequently. I guess the viability of option 1 depends on your aims and circumstances. >Option 4 - Data Reset: reverse the changes via some generic black-magic >after every test. >The dream! Fast, efficient, generic! :) Oh yes, this is what we want. >4b) DB tricks, triggers and al. >You need a DB that is a copy of your baseline stable data. (Might be in the >same DB, but with table names prefixed with 'STBL'(stable)). The only downside I see to this is the complexity it brings, and I'm not sure I want to add much more complexity to my testing environment. I guess it's a trade off: extra complexity for time gained from something like option 1. Regards Richard
Message: 5 Date: Tue, 31 Jul 2001 09:40:11 +0100 From: Neil SwinglerSubject: RE: testing database operation Well I have just finished my first EJB project and this is what I did. DB layer rollback after each test in tearDown Entity EJBs manage transactions using UserTransaction so that I know when ejbLoad and ejbStore are going to happen delete created objects in tearDown, also in setUp in case a previous test bombed out test getters before committing and after commiting to check correct function of ejbStore and ejbLoad Session EJBs manage transactions using UserTransaction but start the transaction in setUp and rollback in tearDown I didn't use any mock objects and this makes the tests expensive to set up and slow to execute. For the DB layer I think I would always want to test against a real database to verify that the SQL is correct. For the EJB layer, next time I would not test the EJBs in the container and I would mock out the DB layer. -- Neil Swingler > -----Original Message----- > From: Lindanger Trond Kjetil > [mailto:trond-kjetil.lindanger@...] > Sent: 31 July 2001 09:50 > To: 'junit@yahoogroups.com' > Subject: RE: [junit] testing database operation > > > Interesting to see what you did Neil. > Did you ever have problems with tests breaking down (e.g. uncaught > NullPointerException) > never reaching tearDown for rollback? > If you never commit it doesn't matter. That's why you set autoCommit to false when testing the db layer. In the EJB layer, if you use UserTransaction, a commit won't happen unless you call commit(). In both cases the explicit rollBack() is not really needed but will presumably free up resources sooner. -- Neil
Date: Fri, 31 Aug 2001 18:33:30 +0100 From: Richard Dallaway Subject: database testing with JTA/JTS A while ago I had an email conversation with Bruce Vinchon regarding database testing. I said I'd mention something we discussed on this mailing list so here goes (and apologies to Bruce if I've misrepresented his thoughts). Bruce mentioned using the "auto commit off" trick to make database testing easier. I.e., you do something like turn auto commit off during setUp() and do an rollback/abort in tearDown(), so you can write to a test database and not have to worry about cleaning it up. Something like that. I replied talking about the problems when you have methods which are themselves transactions (i.e., methods which start transactions, write to various tables, then commit). How can you use the "auto commit off" trick in those situations? Bruce suggested that you may be able to get something to work with nested transactions, perhaps using a transaction service such as JTA/JTS. Sounds good to me, but I haven't had the opportunity to investigate this idea or the Java Transaction APIs. Can anyone comment on this approach? Regards Richard
Date: Fri, 31 Aug 2001 10:48:20 -0700 From: "Yang Wang"Subject: RE: database testing with JTA/JTS I do not think JTA support nested transactions. In the case of nested transactions, it will suspend the outer transaction, begin the inner transaction, commit it and then resume the outer transaction. Maybe new JTA version supports nested transaction. Yang
Date: Fri, 31 Aug 2001 14:10:20 -0400 (EDT) From: Eric VoughtSubject: Re: database testing with JTA/JTS In the past, I've designed my database interaction classes to take an application specific Transaction object. The Transaction had Commit/Rollback methods which contained application specific handling (like resetting local state in event of a database rollback) as well as performing the real commit/rollback . By substituting a special purpose Transaction object I could simulate nested transactions or dummy out the commits for testing purposes.
Date: Mon, 3 Sep 2001 11:46:32 +0200 From: "Bruce Vinchon"Subject: Re: database testing with JTA/JTS hi all, As richard said, there could be several approach to test a database : 1) I just want to make some test and rollback (autoCommit(false) in setup() and rollback() in tearDown()). It allows to insert, update, delete in my database during my tests and rollback all operations to let the database clean. If you do a lot of tests (testSuite), you could just have independant tests (as it should be). If you want to chain tests that uses the same connection and to rollback all operations at the end, you probably should developed a transaction object. Eric describes it. You can control autoCommit and rollback and inhibit their standard behavior. a testSuite can control commit and rollback through other methods in the transaction class (globalCommit() and globalRollback() called in the body of your testSuite ?) as each test methods call inactive autoCommit(), commit() and rollback() methods. 2) you don't want to borrow about it and want to manage nested transactions. well... JTS specification allows nested transaction (spec 1.0 says : "not required" but you can find JTS compliant platforms that support it) : "A JTS transaction manager must support flat transactions; support of nested transactions is optional. If a client begins a transaction, and within that transaction begins another transaction, the latter operation will throw a NotSupportedException if the JTS implementation does not support nested transactions. Keep in mind that even if the JTS implementation supports nested transactions, this transaction manager-level support does not guarantee support for nested transactions in an application. For example, the EJB 1.1 specification does not support nested transactions."... [find on jGuru] but EJB 2.0 supports it ! (http://www.wap-shareware.com/sjug/ejb/index.shtml - see point 6). so where is the problem ? you should find a database that supports it and a transaction server that is JTS compliant. DB2 and SQL Server seems not to support nested transactions (perhaps directly ?). Oracle 8i does... with a little trick : the save points. 3) With save points, you could simulate nested transactions. Oracle 8i allows it in PL/SQL procedure that you could call through JDBC (not good for simple tests !) (see http://www.inquiry.com/techtips/oracle_pro/10min/10min1100.asp). [Informix Universe 9.4 supports the same thing : see http://www.inactsys.com/universe.htm]. Where to find some transaction application server that allows it ? Some products like PowerTiers provides setSavePoints() methods (you can read here for an explanation : http://www.persistence.com/powertier/devpit/documentation/programming/directt5.htm). But you should use it with an appropriate database. Conclusion : as it seems not quite easy to find a cool platform where nested transactions are automatically supported, the first solution seems the best approach actually. Any comments or ideas ? regards, Bruce Vinchon
Date: Tue, 4 Sep 2001 06:13:42 -0700 (PDT) From: Steven MorganSubject: subtransactions are very different > 2) you don't want to borrow about it and want to manage nested > transactions. well... JTS specification allows nested transaction (spec > 1.0 says : "not required" but you can find JTS compliant platforms that > support it) : > Bruce suggested that you may be able to get something to work with nested > transactions, perhaps using a transaction service such as JTA/JTS. Sounds > good to me, but I haven't had the opportunity to investigate this idea or > the Java Transaction APIs. > > Can anyone comment on this approach? OTS subtransactions are very different from top-level transactions. Resources are not consulted and no two-phase commit occurs. Subtransactions simply promote registered resources if they commit and discard them if they rollback. You will need to use subtransaction-aware resources (which are not resources, go figure) to interact with the transaction manager for a subtransaction. And that is far more than you wanted to learn that much about the OMG Transaction Spec, I bet. -Steve :)
Date: Tue, 4 Sep 2001 17:24:59 +0200 From: "Bruce Vinchon"Subject: Re: bruce's sub-trance actions :) > OTS subtransactions are very different from top-level transactions. > Resources are not consulted and no two-phase commit occurs. > Subtransactions simply promote registered resources if they commit and > discard them if they rollback. You will need to use subtransaction-aware > resources (which are not resources, go figure) to interact with the > transaction manager for a subtransaction. And that is far more than you > wanted to learn that much about the OMG Transaction Spec, I bet. you're totally right ! As i've dived into several specification documents (not comments about them), i've discovered that OTS doesn't support directly nested transaction but only subtransaction (as you said). And JTS is based upon OTS. So it explains the "nested transactions not required". About EJB 2.0 spec, it is written : "no nested transactions supported". Authors explain that they want EJB spec to be implemented on any database system. They add that EJB spec could support nested transactions if databases support them [today, Oracle and Informix begin to do it via SavePoints]. So a lot of my mail was wrong (shame on me :() but the conclusion :)) [Long live simple JDBC ! :)] Without enough "normative oxygen" to stay in spec documentation :) i've discovered some research works that explains how to manage nested transaction with OMG database [http://citeseer.nj.nec.com/tesch97global.html (ODMG but works for OMG too)]. but it's another story... thanks Steven ! regards, bruce.
Date: Tue, 25 Sep 2001 10:25:37 +0200 (MEST) From: Johannes LinkSubject: Re: Test-first paper > Here's something I've been working on. It's a paper that talks about some > techniques for developing a database application test-first. The example > code is in the cvs respository on sourceforge, accessible via > http://www.mockobjects.com. > > http://www.mockobjects.com/papers/jdbc_testfirst.html > > Comments, etc? > > Steve Steve, I have tried to write a similar chapter for the test-first book I'm writing at the moment. What evetually made me drop the mock approach for JDBC code is: - The setup of the mocks was more and more of a nightmare. If you really want to test the correct usage of JDBC, you have to check state-dependent behaviour across objects. For instance, you have to check that a statement won't be reused if its connection has been closed, or that your statement has not been reused for another query to execute before you have read all the results; and so on and on. - The test code got too brittle for my taste. During refactoring I constantly changed the way how I queried the database and handed on result sets etc. So I found myself changing the test code again and again. I think your example is too stable to show this disadvantage. - Testing that the SQL is right requires some kind of SQL-Parser. It's not a problem for static SQL strings but it is one when SQL code is somehow assembled dynamically - which was the case in the example I had chosen and which seemed to be natural when striving for the simples thing. The problem might be that JDBC itself was designed without test first and testability in mind. One might probably come up with an API that is much better suited for testing. Johannes
Date: Wed, 26 Sep 2001 21:16:20 +0100 From: "Steve Freeman"Subject: Re: Testing SQL: was Test-first paper > Surely the only way to test SQL is to use a database. Imagine testing > java code by parsing it rather than running it. Maybe CMP EJBs make this > a lot easier. but there are two things going on here: checking that the right things go into the generated SQL and checking that the SQL is in sync with the schema. In fact, code generation is an interesting example because the unit tests for the Big Ben tools (which generate test suites) just does some simple string matching -- there are higher level tests to make sure the thing runs. In my experience, most dynamic SQL is some static stuff with values, and maybe an extra clause, stuffed in. It may not be worth testing the static stuff during SQL generation, but it is worth gaining enough confidence that the stuffing is working -- and often the minimum working test can be a substring match. I've seen effective unit testing of HTML done with regular expressions. Now the corollary of this is that at some point you have to be sure that it all hangs together (as against hanging seperately...). My suspicion is that most applications of any size end up with a data model layer, which can be sanity checked by a smaller cross-section of integration (or whatever we're calling them now) tests. It sounds like your approach is to concentrate on more thorough integration tests. What's the point of the extra tests? - we test everything that can break and some things, like forcing exceptions, are very hard to test against a real server. I find that testing with mocks pushes me towards thinking harder about things like error handling. - those of us who use them regularly find that the discipline of writing Mock Objects has a good effect on the shape of our code. I'd be loath to give that up. - as we discover new classes and factor them out, we can see the tests that go with them and move them too. Steve