This particular example came from a request to add an "Exclude Lines from Coverage" feature to OpenCover. Now there are many ways this could be achieved, none of which I had any appetite for as they were either too clunky and/or could make OpenCover very slow. I am also not a big fan on excluding anything from code coverage; though OpenCover has several exclude options I just thought that this was one step too far in order to achieve that 100% coverage value as it could too easily abused. Even if I did think the feature was useful it still may not get implemented by myself for several days, weeks or months.
But sometimes there are other ways to cover your code without a big refactoring and mocking exercise which can act as a deterrent to doing the right thing.
In this case the user was using EntityFramework and wanted to exclude the code in the catch handlers because they couldn't force EntityFramework to crash on demand - this is quite a common problem in my experience. The user also knew that one approach was to push all that EntityFramework stuff out to another class and could then test their exception handling via mocks but didn't have the time/appetite to go down that path and thus wanted to exclude that code.
I imagined that the user has code that looked something like this:
public void SaveCustomers(ILogger logger) { CustomersEntities ctx = CustomersEntities.Context;//) try { // awsome stuff with EntityFramework ctx.SaveChanges(); } catch(Exception ex) { // do some awesome logging logger.Write(ex); throw; } }
and I could see why this would be hard (but not impossible) to test the exception handling. Now instead of extracting out all the interactions with the EntityFramework so it is possible to throw an exception during testing I suggested the following refactoring:
internal void CallWrapper(Action doSomething, ILogger logger) { try { doSomething(); } catch(Exception ex) { // do some awesome logging logger.Write(ex); throw; } }
which I would then use like this:
public void SaveCustomers(ILogger logger) { CustomersEntities ctx = CustomersEntities.Context;//) CallWrapper(() => { // awsome stuff with EntityFramework ctx.SaveChanges(); }, logger); }
My original tests should still continue as before and now I have a new method that I can now test independently.
I know this isn't the only way to tackle this sort of problem and I'd love to hear about other approaches.
No comments:
Post a Comment