Monday 24 November 2008

Unit testing private methods.. not again!

This is a question often raised on TDD forums. How do I test a private (protected, internal, Friend) method. Those not so bold ask, should I? A typical answer is, if you want to test a private method, extract it into another class and make it public, and make the first class hold a private instance of this new class.

I think that this answer is too general. While newbies generally want a universal recipe (and this one is good enough), I'd start gathering more info. Like a Zen master wannabe, I'd ask a question which should be an answer to the original one. Oh, and then I'd ask some more.

So, why do you want to test it?

Is it just the idea that every method in a class should be tested? Then resist this temptation.

Another option is that your public method that calls your private method just started acting weird. Well, the next question, what portion of the code just changed that caused this behavior? Still another option, that you dared to write some code without writing the test first, and now you're trying to cover your ass. My humble advice would be to delete the bastard and start all over.

But there are several other important questions to ask. Does the private method have a well defined semantic meaning? If yes, why do you resist making it public? Are you afraid that someone will call it? Why? Are you lazy to document it?

The only reason I can accept here is that your private method puts a system into an inconsistent state. For example, you can have a public Transfer method, which takes money from one account and moves it to another. The two private methods would be manipulation the two respective accounts. If you call one of them, some money would be, say, taken from one account but not moved to the other. In this case, it is unacceptable to move the method to another class and make it public: calling it from outside would be a disaster. In this case, I would simply refactor the system so that no such method exists.

As this begins to sound very confusing, I'd give some reasons for choosing what to test.
  • A test is a usage example, a documentation in an executable form. A unit test is, more or less, a feature: you provide a code example on how to use your product. This might be wrong for desktop or Web applications, but the essence remains the same. It would be weird to provide a usage example for a private method.
  • One of the main reasons for hiding a method is "encapsulation", as the proponents put it. In other words, you want to hide the implementation details. This is usually a good idea, given that you have a solid reason to do so. Typically, the reason is that you might change it in the future. So, the simple logic conclusion is that you shouldn't test it, since it makes your tests brittle. On the other hand, if you make it public, nothing prevents you from making a changed version later, and keeping this one unchanged (or maybe enhanced, but keeping the same functionality).
  • Still another reason: if I make all these methods public, my API will be bloated, and my customers confused. This is a valid reason only for those who are lazy enough to write a good doc. Other possible solutions are: use the EditorBrowsableAttribute; move this method to another class (here we go again!), and place this class in the Internals namespace (or invent some ever more scary name).

Monday 10 November 2008

Localizing the GridView date values

This is weird. I should have bumped into this problem ages ago. The problem is, whenever I'm trying to enter a localized date into a datagrid, it is saved as if it were an American date. I.e., 1.02.2008, which is the 1st of February, is saved as 01/02/08 (January 2nd).

Setting the page's Culture to Russian didn't work. A quick Reflectoring showed that ObjectDataSource uses InvariantCulture for parsing the supplied values.

The solution is sort of ugly: use the grid's Updating event and swap the string with the corresponding date:
    Protected Sub gridDjSchedules_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs)
        e.NewValues("TheDate") = Convert.ToDateTime(e.NewValues("TheDate"), System.Globalization.CultureInfo.CurrentCulture)
    End Sub

Looks like it's time to create a custom control..