views:

223

answers:

8

Hello,

I have a .NET application with a web front-end, WCF windows service back-end. The application is fairly simple - it takes some user input, sending it to the service. The service does this - takes the input (excel spreadsheet), extracts the data items, checks SQL db to make sure the items are not already existing - if they do not exist, we make a real-time request to a third party data vendor and retreive the results, inserting them into the database. It does some logging along the way.

I have a Job class with a single public ctor and public Run() method. The ctor takes all the params, and the Run() method does all of the above logic. Each logical piece of functionality is split into a seperate class - IParser does file parsing, IConnection does the interaction with the data vendor, IDataAccess does the data access, etc. The Job class has private instances of these interfaces, and uses DI to construct the actual implementations by default, but allows the class user to inject any interface.

In the real code, I use the default ctor. In my unit tests for the Run() method, I use all mock objects creating via NMock2.0. This Run() method is essentially the 'top level' function of this application.

Now heres my issue / question - the unit tests for this Run() method are crazy. I have 3 mock objects im sending into the ctor, and each mock object sets expectations on themselves. At the end I verify. I have a few different flows that the Run method can take, each flow having its own test - it could find everythings already in the database and not make a request to vendor... or an exception could be thrown and the job status could be set to 'failed'.. OR we can have the case where we didnt have the data and needed to make the vendor request (so all those function calls would need to be made).

Now - before you yell at me and say 'your Run() method is too complicated!' - this Run method is only a mere 50 lines of code! (It does make calls to some private function; but the entire class is only 160 lines). Since all the 'real' logic is being done in the interfaces that are declared on this class. howeveer, the biggest unit test on this function is 80 lines of code, with 13 calls to Expect.BLAH().. _

This makes re-factoring a huge pain. If I want to change this Run() method around, I have to go edit my 3 unit tests and add/remove/update Expect() calls. When I need to refactor, I end up spending more time creating my mock calls then I did actually writing the new code. And doing real TDD on this function makes it even more difficult if not impossible. It's making me think that it's not even worth unit testing this top level function at all, since really this class isn't doing much logic, it's just passing around data to its composite objects (which are all fully unit tested and don't require mocking).

So - should I even bother testing this high level function? And what am I gainging by doing this? Or am I completely mis-using mock/stub objects here? Perhaps I should scrap the unit tests on this class, and instead just make an automated integration test, which uses the real implementations of the objects and Asserts() against SQL Queries to make sure the right end-state data exists? What am I missing here?

Thanks very much for any help!

EDIT: Here's the code - the first function is the actual Run() method - then my 5 tests which test all 5 possible code paths. I changed it some for NDA reasons but the general concept is still there. Anything you see wrong with how I'm testing this function, any suggestions on what to change to make it better? Thanks.

+1  A: 

The first thing I would try would be refactroing your unit tests to share the set up code between tests by refactoring to a method that sets up the mocks and expectations. Parameterize the method so your expectations are configurable. You may need one or perhaps more of these set up methods depending on how much alike the set up is from test to test.

tvanfosson
I expect, then, that you are doing too much in a single method. You might want to refactor the method into smaller, logically consistent methods that your Run() method calls. Unit test these methods and integration test your Run() method.
tvanfosson
I suppose this is an option. But Run() is the only public method exposed on my Job class. If I broke it into multiple private methods called inside of Run(), then I would wind up having to write unit tests for private functions. Doesn't this break the 'test behavior and not implementation' paradigm of TDD?
dferraro
I guess I'm more pragmatic than I am ideologic. If I need to break up the method to improve testability and it means that I need to test private methods, then I'll test private methods. Having good, clean, readable code with short, focused methods is more important to me than always following the paradigm.
tvanfosson
you're right! Thanks again for the help everyone! If I could have given 2 people the 'answer' check box you would have gotton it too =P
dferraro
A: 

Your tests are too complicated.

You should test aspects of your class rather than writing a unittest for each member of yor class. A unittest should not cover the entire functionality of a member.

winSharp93
could you give more details on what you mean by aspects? All this function does is pass around variables to composite objects, for the most part, as well as does error handling. So I write some unit tests for this function, by mocking out these composite objects, and forcing errors, proper flow, etc.. Isn't this exactly whta I should be testing, since this is exactly what I expect my method to do? (pass around data to its members and handle error)
dferraro
I believe what winSharp93 is saying is, only test one small part of the method at a time, break it up in to smaller tests even if the method is still complicated. Ie can I check that the IParser mock is called with as few other stubs being filled out as possible. Try to have only one mock'd call in your test with everything else a stub. Another way to say it is ignore as much internal workings as possible with each test and only focus on one small specific tidbit of logic, this way if all the rest of the method changes, but the tidbit is the same, the test still passes.
MarcLawrence
Thanks... Just to be devils advocate though... Even if I broker it into smaller chunks and was able to re-use as much chunks as possible in each test (or broke it into multiple tests)..What am I really testing here? All it's doing is making sure that each method is called and is passed the right parameters. What's the point of this test really? Wouldn't an automated integration test, where I use real implementations instead of mocks, run a job with pre-defined params, then having asserts() at the end on end-state database tables, be much more valueable and useful? And make this mocked test
dferraro
.. completely not needed?
dferraro
Well - if your run method looks like `DoFoo(); DoBar(); DoBaz();´ you might first write a test like (dummy-code) Expect(DoFoo), Expect(DoBar), Expect(DoBaz). However, you should write three test: One called maybe "RunCallsFoo", "RunCallsBar" and "RunCallsBaz". Then maybe Foo must be called before Bar - so there comes a fourth test. Those tests look not really neccessary at first. However, imagine you want to parallize the method in the future. Now they ARE important - parallizing might lead to a situation where Foo is not called before Bar. So tests can be a part of the documentation, too.
winSharp93
... Especially when using TDD the tests become more than only tests: They help you finding out how a method should really work: Is it really neccessary that Foo executes before Bar? What happens if there is an exception executing Foo? Should Baz still be called? So according to TDD: You should test everything before you write it - even if it seems very obviously.
winSharp93
I am testing all possible code paths, all in seperate tests. I've pasted the code to pastebin and linked above. Any advice is greatly appreciated. I've also added a bounty. Thanks again!
dferraro
+2  A: 

Two thoughts: first you should have an integration test anyway to make sure everything hangs together. Second, it sounds to me like you're missing intermediate objects. In my world, 50 lines is a long method. It's hard to say anything more precise without seeing the code.

Steve Freeman
A: 

I'm going to guess that each test for Run() set expectations on every method they call on the mocks, even if that test doesn't focus on checking every such method invocation. I strongly recommend you Google "mocks aren't stubs" and read Fowler's article.

Also, 50 lines of code is pretty complex. How many codepaths through the method? 20+? You might benefit from a higher level of abstraction. I'd need to see code to judge more certainly.

J. B. Rainsberger
There's only 4 tests and 4 code paths for this method... It's doing literally the entire work of the system, (albeit a small-mid sized app). It has 4 composite objects, which are interfaces that it calls to do all the actual work. It just seems like the tests i write on this upper level function are useless; the tests read just like the actual function, and since im using mocks, its only asserting the functions are called and sent params (i can check this by eyeballing the 50 line function). It seems i should not be using mocks and instead use implementation, then make assert calls against the
dferraro
dferraro
+1  A: 

So - should I even bother testing this high level function?

Yes. If there are different code-paths, you should be.

And what am I gainging by doing this? Or am I completely mis-using mock/stub objects here?

As J.B. pointed out (Nice seeing you at AgileIndia2010!), Fowler's article is recommended read. As a gross simplification: Use Stubs, when you don't care about the values returned by the collaborators. If you the return values from the collaborator.call_method() changes the behavior(or you need non trivial checks on args, computation for return values), you need mocks.

Suggested refactorings:

  1. Try moving the creation and injection of mocks into a common Setup method. Most unit testing frameworks support this; will be called before each test
  2. Your LogMessage calls are beacons - calling out once again for intention revealing methods. e.g. SubmitBARRequest(). This will shorten your production code.
  3. Try n move each Expect.Blah1(..) into intention revealing methods. This will shorten your test code and make it immensely readable and easier to modify. e.g. Replace all instances of .

    Expect.Once.On(mockDao) _ .Method("BeginJob") _ .With(New Object() {submittedBy, clientID, runDate, "Sent For Baring"}) _ .Will([Return].Value(0));

with

ExpectBeginJobOnDAO_AndReturnZero(); // you can name it better

Gishu
+1  A: 

on whether to test such function: you said in a comment

" the tests read just like the actual function, and since im using mocks, its only asserting the functions are called and sent params (i can check this by eyeballing the 50 line function)"

imho eyeballing the function isn't enough, haven't you heard: "I can't believe I missed that!" ... you have a fair amount of scenarios that could go wrong in that Run method, covering that logic is a good idea.

on tests being brittle: try having some shared methods that you can use in the test class for the common scenarios. If you are concerned about a later change breaking all the tests, put the pieces that concerned you in specific methods that can be changed if needed.

on tests being too long / hard to know what's in there: don't test single scenarios with every single assertion that its related to it. Break it up, test stuff like it should log x messages when y happens (1 test), it should save to the db when y happens (another separate test), it should send a request to a third party when z happens (yet another test), etc.

on doing integration/system tests instead of these unit tests: you can see from your current situation that there are plenty of scenarios & little variations involved in that part of your system. That's with the shield of replacing yet more logic with those mocks & the ease of simulating different conditions. Doing the same with the whole thing will add a whole new level of complexity to your scenario, something that is surely unmanageable if you want to cover a wide set of scenarios.

imho you should minimize the combinations that you are leaving for your system tests, exercising some main scenarios should already tell you that a Lot of the system is working correctly - it should be a lot about everything being hooked correctly.

The above said, I do recommend adding focused integration tests for all the integration code you have that might not be currently covered by your tests / since by definition unit tests don't get there. This exercises specifically the integration code with all the variations you expect from it - the corresponding tests are much simpler than trying to reach those behaviors in the context of the whole system & tell you very quickly if any assumptions in those pieces is causing trouble.

eglasius
One your second bolded comment: I already have 5 seperate tests for that function for all the scenarios (if x happens expect this in one, else.... in the others). Am I misunderstanding what you are suggesting then? Maybe you could give some brief psuedocode examples?On the last comment... I think writing an automated integration test for this function would be easy and serve as a great automated 'smoke test'. I would have a single test which is for the flow 'when evertying works'. I run my job with actual implementations instead of mocks, and then at the end of test. I called a stored proc
dferraro
which populates a gridview on a web application to display the end state results of that job running. Then I make assert calls against the fields, making sure that that proc is returning what I expect with the given input file... Does this sound good? And at what point do I balance 'youre trying to write too many integration tests' with 'you don't have enough'? I realize that that's a subjective question; I'm guessing it just a takes common sense approach...
dferraro
also, thanks for the response. I have absolutelly nooooo idea about the 'how did I miss that' moment </sarcasm>. Great point.
dferraro
+1  A: 

I guess my advice echos most of what is posted here.

It sounds as if your Run method needs to be broken down more. If its design is forcing you into tests that are more complicated than it is, something is wrong. Remember this is TDD we're talking about, so your tests should dictate the design of your routine. If that means testing private functions, so be it. No technological philosophy or methodology should be so rigid that you can't do what feels right.

Additionally, I agree with some of the other posters, that your tests should be broken down into smaller segments. Ask yourself this, if you were going to be writting this app for the first time and your Run function didn't yet exist, what would your tests look like? That response is probably not what you have currently (otherwise you wouldn't be asking the question). :)

The one benefit you do have is that there isn't a lot of code in the class, so refactoring it shouldn't be very painful.

EDIT

Just saw you posted the code and had some thoughts (no particular order).

  1. Way too much code (IMO) inside your SyncLock block. The general rule is to keep the code to a minimal inside a SyncLock. Does it ALL have to be locked?
  2. Start breaking code out into functions that can be tested independently. Example: The ForLoop that removes ID's from the List(String) if they exist in the DB. Some might argue that the m_dao.BeginJob call should be in some sort of GetID function that can be tested.
  3. Can any of the m_dao procedures be turned into functions that can tested on their own? I would assume that the m_dao class has it's own tests somewhere, but by looking at the code it appears that that might not be the case. They should, along with the functionality in the m_Parser class. That will relieve some of the burden of the Run tests.

If this were my code, my goal would be to get the code to a place where all the individual procedure calls inside Run are tested on their own and that the Run tests just test the final out come. Given input A, B, C: expect outcome X. Give input E, F, G: expect Y. The detail of how Run gets to X or Y is already tested in the other procedures' tests.

These were just my intial thoughts. I'm sure there are a bunch of different approaches one could take.

Walter
Thanks for the reply. You're right though - this is the only function in my application that I write the test for AFTER writing the code when I create enhancements. Perhaps there's a better reason for this then 'its the top level function so it's just too damn hard to write the test first for'. Also, because of the Expect() calls in the mocking framework, I more easily get away with testing after because I'm not worried about making sure my test will fail first
dferraro
thanks again Walter. This is def. the answer to my problem - even 50 lines of code is too much for one function here. tvanfosson and others mentioned this as well in their replies so it's tough to decide who gets the bounty. But the real solution would be to split up the Run method into seperate private methods and test those private methods. At first i was worried about breaking the 'test behavior' paradigm; but tvanfosson is correct, and common sense and gut instinct should prevail over 'following the rules'. Thanks again everyone!
dferraro
+1  A: 

If you think unit-tests are too hard, do this instead: add post-conditions to the Run method. Post-conditions are when you make an assertion about the code. For example, at the end of that method, you may want some variable to hold a particular value or one value out of some possible choices.

After, you can derive your pre-conditions for the method. This is basically the data type of each parameter and the limits and constraints on each of those parameters (and on any other variable initialized at the beginning of the method).

In this way, you can be sure both the input and output are what is desired.

That probably still won't be enough so you will have to look at the code of the method line by line and look for large sections that you want to make assertions about. If you have an If statement, you should check for some conditions before and after it.

You won't need any mock objects if you know how to check if the arguments to the object are valid and you know what range of outputs are desired.

omouse
So you are saying to add the test code directly into the Run() method? Isn't putting Assert statements in production code a bad idea? Or.. ?
dferraro
It isn't a bad idea, especially if you're having problems like you are now. After you're sure they work, just comment them out. Or you can do it all manually without using the assert function just by writing in the comments what the expected results/state-changes are. The point is to walk through and know what's going on at each spot. You should be able to just use pen, paper and manuals/references to do it :P
omouse