views:

164

answers:

4

I have a method that is called on an object to perform some business logic and add it to the database.

The object is a Transaction, and part of the business logic requires searching the databses for related accounts and history items on the account.

There are then a series of comparisons and operations that need to bring back information from the account and apply it to the transaction before the transaction is then passed on to other people and written to the database.

The only way I can think of for testing this currently is within the test to create an account and the relevant history information, then to construct a transaction for each different scenario and capture the information written to the DB for the transaction and information being passed on, however this feels like its testing way too much in one test. Each scenario would be performed in a separate unit test, with the test construction refactored out into separate methods, but the actual piece of code targetted by the test is over 500 lines long.

I guess this question is more about refactoring than unit testing, but in this case they go hand in hand.

If anyone has any advice (good or bad) then I'd be glad to hear it.

EDIT:

Pseudo code:

Find account for transaction 
Do validation on transaction codes and values 
Update transaction with info from account 
Get related history from account Handle different transaction codes and values (6 different combinations, each with different logic) 
Update the transaction again with new account info (resulting from business logic) 
Send transaction to clients
A: 

This depends on what you would like to test. Would you like to test the database transaction? Would you like to test the business transaction or something else? Try to use mockups for things you would not like to test. With mockups you can concentrate on certain test objectives.

Mork0075
There is a lot of complex logic about what is sent out and what is saved, and how the account is updated. I need to be able to test all of this, as any changes could cause chaos.
ck
+4  A: 

I would appreciate it if you had some pseudocode on this question, but just following it over I would:

  • Create interfaces for the data access objects that directly access the database - this way you can pass in an object that only pretends (e.g., mocks) that it accesses the database. This object would then return results consistent with the results your database would return, without actually doing any DB call. Your object could also simulate scenarios such as rolling back data to its original state.
  • Extract each "scenario" into a single method each - that is the essence of a unit. If your method is 500 lines long then there must be contiguous blocks in there that can be extracted. Write a unit test for each, if appropriate.
  • If your unit test is testing too much, that probably means your method is doing too much - You can extract methods by identifying the different things you are testing and then putting them in their own methods. Rinse and repeat until you only need one test for each method.
  • Transactions "passed on to other people" sounds like a code smell - a transaction in and by itself should only be one contiguous unit. If you need different users to finish your transaction, you're doing too much; keep track of your data's state on the DB instead, in terms of flags or such, not in terms of a DB transaction.
Jon Limjap
Thanks Jon, I'll try to add some pseudo code (without giving away too much IP...) Also, in this case, a transaction is a financial transaction, not one relating to atomic database operations.
ck
A: 

Yes you -can- rewrite you current code so that it can be unit tested according to all guidelines and best-practices.

However, that can be expensive, and You should estimate the cost and compare that against the earnings...

The earnings is that you might discover a problem with the code and also, if done right, the reduction of the complexity as the result of the refactoring.

Both factors might save some time - in the future.

The cost is the time and effort you have to spend both refactor your code, writing the test cases and also the extra time you might have to spend in the future to maintain the test-cases and the mocking code - and that can be significant costs.

You are comparing a known cost against a future risk and I am sure a lot of smart guys knows how to do that, but it's obvious that You can actually spend an infinite time refactoring and mocking without ever reducing the risk of failure to zero (or even at all if the code and problem is complex and you are messing things up when refactoring), so you need to find a balance here.

In this case, as the code is old, it might be ok to be "sloppy" or "pragmatic" and do black box testing - or top-down testing and just test the interface (or abstraction) without bothering to mock the database. And yes, you can argue that this is not a unit test but instead a system test or a function test practice.

...But, it might give the best value for your money - or your employers/customers money - or more time with your significant other (or at least more time to watch discovery channel.)

If you have old code, allow black box testing, allow dependencies between the tests and compile a sequence of test that sets up the test data and manipulates it, and its at least tested automatically while not tested 100%.

KarlP
If people are going to down vote an entry then follow that up with a comment on why, so the author has the opportunity to improve there work.
Jiminy
+1  A: 

Separating out units from existing legacy code can be extremely tricky and time consuming. Check out Working Effectively With Legacy Code for a variety of tried and tested techniques to make things more manageable.

David Sykes