views:

139

answers:

5

Hi,

in all (Agile) articles i read about this: keep your code and functions small and easy to test.

How should i do this with the 'controller' or 'coordinator' class?

In my situation, i have to import data. In the end i have one object who coordinates this, and i was wondering if there is a way of keeping the coordinator lean(er) and mean(er).

My coordinator now does the followling (pseudocode)

//Write to the log that the import has started
Log.StartImport()
//Get the data in Excel sheet format
result = new Downloader().GetExcelFile()
//Log this step
Log.LogStep(result )
//convert the data to intern objects
result = new Converter().Convertdata(result);
//Log this step
Log.LogStep(result )
//write the data
result = Repository.SaveData(result);
//Log this step
Log.LogStep(result )

Imho, this is one of those 'know all' classes or at least one being 'not lean and mean'? Or, am i taking this lean and mean thing to far and is it impossible to program an import without some kind of 'fat' importer/coordinator?

Michel

EDIT this is actually a two-in-one question: one is how to test it, second is if it's ok to have a 'know all/glue all together' coordinator

+3  A: 

I had a similar problem and the way I solved it was to really have a proper look at the SRP (Single Responsibility Principal).

ExcelFileReader who can read the excel file and return a set of List (lines)

Parser it's job is to parse the lines returned from the ExcelFileReader using the delimiter

Importer which handles importing the DataSet returned from FileParser into the correct tables.

This keeps it in a testable form as ExcelFileReader doesn't depend on the Parser or Importer. This goes for the Parser as well as it's testable by just passing it a TextReader.

Does this help?

PieterG
@PieterG the problem is that one of his classes responsibility is to mediate with other classes. In theory is easy to aggregate many responsibilities in to a one abstract but in practice its not always so easy or developer time friendly.
Adam Gent
I think that's what he did.
Grzenio
This is sort of what i've done so far, i've created classes for each main step (read excel, convert and save). So without knowing SRP i've used it :-). But what remains is that i have a coordinator which takes the excel sheet from the importer, hands it to the converter, get the converted data back, and hand it to the repository. In between is also saves all kinds of data to the logtables, so that coordinator's method ('StartImport') is a 'fat' method.
Michel
+1  A: 

It is very common to have a class that does what I believe is the mediator pattern.

One way I have found to combat classes that have to have collaborators (which is what you are running into right now) is to do Mock Unit Testing.

You essentially Mock your collaborators and set expectations to happen to those collaborators.

Unfortunately you are using C# and I do not know of any Mock libraries for C#. However I am sure google can help you on find a mock library.

Instead of a mock library you can just implement or extend your collaborator's classes and override and implement the methods that you expect to call with methods that produce expected output.

As noted by Michael it makes it far easier if you have dependency injection wiring in your collaborators.

Adam Gent
The mediator pattern looks promising
Michel
+11  A: 

I am sure that many people would disagree, but I think your method is in general lean enough. The crucial bit you are missing here though is dependency injection (also known as inversion of control) - so instead of newing Downloader, Converter etc. inside your method, you should define interfaces for these classes and "inject" them in the constructor of your class:

private Downloader downloader;
private Converter converter;

public MyClass(Downloader downloader, Converter converter)
{
  this.downloader = downloader;
  this.converter = converter;
  //same with repository, it can't be a static class anymore
}

then you just use these instances in your method:

//Write to the log that the import has started
Log.StartImport()
//Get the data in Excel sheet format
result = downloader.GetExcelFile()
//Log this step
Log.LogStep(result )
//convert the data to intern objects
result = converter.Convertdata(result);
//Log this step
Log.LogStep(result )
//write the data
result = repository.SaveData(result);
//Log this step
Log.LogStep(result )

now after doing this change, in your test you can use one of the mocking frameworks (I use RhinoMocks) to inject a mocked implementation of the dependencies into your class. This way you can just verify that the correct method was called on the converter and downloader, without reading anything from disk and parsing any spreadsheets.

Examples of how to use RhinoMocks are in their documentation: http://ayende.com/wiki/Rhino+Mocks+Documentation.ashx

Don't hesitate to ask another question if you get stuck :)

Grzenio
Thanks for your answer. i've read about mocks a lot, but never dared to use it. If i use a mock, i still have to unit test the 'real' Excel reader don't i? At some point i have to test if the downloader actually get's an Excel sheet from some Uri, don't i? Or isn't that test called a unit test anymore but more a integration test?
Michel
I'd create an interface for Log as well. Log is a hidden dependency that cannot be mocked as it is now.
Jakob Christensen
Right, i always get trapped in the logging dependency
Michel
Hi @Michel, (1) yes, you are supposed to write unit tests for every class (2) on top of unit tests you probably want to write some higher level tests (to test integration of the classes): please take a look at this discussion: http://stackoverflow.com/questions/2965483/unit-tests-the-benefit-from-unit-tests-with-contract-changes/2969379#2969379
Grzenio
@Michael and @Jakob There will always be implicit collaborators like logging or native libraries that you do not have to mock and really should not as it is a waste of time.
Adam Gent
@Grzenio thank you so much for this explanation, it's a lot more meatier than mine :)
PieterG
One thing that should be pointed out is that not all code can be unit tested. At some point you're going to have to use some 3rd party object/method that has no interface defined. For instance, if you were to abstract your Log object in to some interface, the implementation of that interface will still need to call `Log.LogStep` and won't be "unit-testable". For this code, you have to write integration tests, which test without any mocking or stubbing. I have heard it said many times that you should "unit test every class", but in reality I have found that this is almost never possible.
Justin Holzer
@justin: nice, that 'unit test every class' is also sometimes giving me hard times.
Michel
A: 

I found this article particularly useful when deciding if my code was indeed lean and mean

Dean
+1  A: 

Your comments and logger (singleton?) are too noisy:

Log.StartImport();
result = new Downloader().GetExcelFile()
Log.Step(result);
result = new Converter().Convertdata(result);
Log.Step(result);
result = Repository.SaveData(result);
Log.Step(result);

And since you have three steps that have to happen together wrap, them up in Converter. Using DI pass in to Converter a Downloader and a Repository. Now you end up with:

Log.StartConvert();
convert.ExcelToRepository(); //passed in with DI

Note all Log step results are done in there respective actions.

Gutzofter
Nice viewpoint.I was wondering wether i could put the logging in the respective actions because i thought maybe the excel converter shouldn't know anythinh about logging because it's not his concern but more the concern of the converter?
Michel
Logging is a cross-cutting concern to begin with. If you are worried about these cross-cutting concerns, you might look at going to a messaging system that has multiple subscribers to a single event publisher. Otherwise push the logging as close the the action as possible. Remember, **Tell, don't ask!**. Follow the *Law of Demeter*.
Gutzofter