Showing posts with label CodeProject. Show all posts
Showing posts with label CodeProject. Show all posts

Monday, 5 July 2010

Testing Asp.Net pages with Telerik's JustMock

It is always nice when a competitor publishes a piece of their code. You can always sneak into the comments and, like, look, I'm better! and put a link to your blog where you show your version proudly, and get a massive following.

Guess what, this is what I'm doing right now commenting on the Mehfuz's post. But the main thing I realized reading his post is that this is the perfect example of how using mocks in certain situations can turn testing into a nightmare. The following is not a problem with JustMock, it's a problem with trying to write a unit test involving a complex framework, and isolating parts of it not meant to be isolated.

(Disclaimer: I do understand that the purpose of the original post is not to teach us how to test Asp.Net pages, but to demonstrate the capabilities of JustMock. So, my intent is not to prove that the author is wrong, but rather to take his example as a perfect situation where mock should absolutely not be used.)

So, in order to write the test, we have to
  1. Mock the HttpBrowserCapabilities class and stub a couple of its properties so that it returns something when needed.
  2. Mock the HttpRequest class (of course) and make the mock return our mocked Browser.
  3. Mock the HttpResponse class as well.
  4. Mock the HttpContext class and stub its Request and Response properties (make the getters return our mock instances).
  5. Finally, we are stubbing the Page.RenderControl method, and it's unclear whether we do it to avoid exceptions, or just for fun.
The main purpose is to test whether the Page instance fires all (well, just some) its lifecycle events, and it's done not using mocks but rather adding event handlers (since we already have our Page instance).

Why do we have to mock all these classes? Why do we have to fire up Reflector and dig into the Framework source in order to make our test pass? Because we have this idea that unit testing means testing a particular class in isolation. Somehow all calculator examples left us with an idea that "unit" == "class".

Now, let's back off a bit and consider this: we are testing a high-level ProcessRequest method, which does a lot of lower-level calls to various classes, which are tightly coupled with each other. So, I think it's logical to say that our "unit" is a good part of the System.Web assembly. With that assumption, everything becomes simple: we don't test just our Page instance, we test the whole unit. Assuming we are the developers of the framework, we can also test the lower level methods which have less dependencies, so that we can mock them more easily.

Back to our integration test, here's what it looks like when we use Ivonna. I have omitted all asserts for clarity; instead, we just write messages to the console, as we would in an exploratory test:
[Test]
public void TestingPageEvents()
{
 var session = new TestSession();
 var request = new WebRequest("Default.aspx");
 request.EventHandlers.Page_Init = 
             (sender, e) => Console.WriteLine("Init fired");
 request.EventHandlers.Page_Load = 
             (sender, e) => Console.WriteLine("Load fired");
 session.ProcessRequest(request);
}


This code executes a test against a "real" Web page, with a "real" HttpContext and other necessary objects, so it gives us much more valuable information about the behavior of our system in "real world" situations.

Check out a fresh version of Ivonna here.

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).

Tuesday, 29 July 2008

A Dose Of Code

While developing Ivonna, I often have to figure out how various stuff in System.Web works. Sometimes Reflectoring it is enough; however, there are some particularly gigantic methods, full of loops and branches, that are hard to figure out. In any case, Reflector gives a static picture, while I need to see it live. Sure now you can download the source and debug through it (although I've never managed), but the real hackers don't use debuggers, preferring asserts and console output.

Anyway, I needed to inject some code (or stop the debugger) at some particular method call in some assembly I don't have the code of. I've been using the TypeMock's MockMethodCalled event for it. After a while, it became tedious, so I decided to put some repetitive code into a class. Soon I found that it could have a couple of more useful features, but I wanted it to be really simple, and I didn't need much, so I'm keeping it to one class, three methods, three relaxed evenings of development.

How does it work.

  1. Create an instance of Njector using the target type (this is something like creating a mocked instance).
  2. Add one or more injected pieces of code (something similar to adding expectations).
  3. Call the main code, in which an instance of the target type is created, and the target methods are called.
  4. The injected pieces are invoked before, after, or instead of the target methods. You also gain access to the target instance, target method parameters, and return value (in case you use the After injection).


Example
This is something that I actually used to figure out how the System.Web.HttpMultipartContentTemplateParser class parses the input using the ParseIntoElementList method. This method has several loops and branches, so it's not easy to find out what's happening given a particular piece of data. What you see here is just the preparation; after that I prepare the input data and call the framework code using Ivonna, but that's outside the scope of this post. GetPrivateField() is an utility extension method doing guess what.

var web = Assembly.GetAssembly(typeof(System.Web.HttpRequest));

var parserType = web.GetType("System.Web.HttpMultipartContentTemplateParser", true, true);

var inject = new Njector(parserType);

inject.After("GetNextLine", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("GetNextLine: {0}", retValue);

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

Console.WriteLine("partDataStart={0}", target.GetPrivateField("_partDataStart"));

});

inject.After("AtBoundaryLine", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("AtBoundaryLine: {0}", retValue);

});

inject.After("AtEndOfData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("AtEndOfData: {0}", retValue);

});

inject.After("ParsePartHeaders", null, delegate( object target, MethodBase method,object[] parameters, object retValue) {

Console.WriteLine("ParsePartHeaders");

});

inject.Before("ParsePartData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("ParsePartData Before");

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

//System.Diagnostics.Debugger.Break();

});

inject.After("ParsePartData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("ParsePartData After");

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

Console.WriteLine("partDataLength={0}", target.GetPrivateField("_partDataLength"));

});

//Prepare the data

//Do the POST

//The framework creates an instance of System.Web.HttpMultipartContentTemplateParser and calls its ParseIntoElementList method,

//which in turn calls the methods like GetNextLine, AtBoundaryLine, etc.

//After the GetNextLine method is called, our code is executed, and we're able to see the result.


Download the source here.

Update: there's a much better project now, called CThru, developed by TypeMock.

Thursday, 29 May 2008

Multiple AppDomains on a single Web

I was getting some weird errors when using a Neo ObjectContext on a Web as a global variable. This thing is supposed to be dependency-injected into factories and stuff, but I've set it to be a static property long time ago and saved me a lot of effort, since most stuff became possible with the Neo code generation tool.

This was long before I learned that singletons are evil.

Anyway, I've been getting very weird errors. Such as, I see a page with a record that should have been deleted, I refresh the page and it's not there, I refresh it again and it's there etc. It looked as if there were two contexts, and the record has been deleted from only one of them. However, since a Context was a static variable, there couldn't possibly be two contexts, right?

Or so I thought. Recently, I've been investigating the whole remoting thing in connection with Ivonna, and I discovered the trivial fact that different static variables exist in different AppDomains. So, I thought that maybe there are two AppDomains for the same site? A quick experiment showed that this is true indeed, if there are two (almost) simultaneous requests.

And the moral of the story is.. dunno, will figure out tomorrow.

Sunday, 11 May 2008

Activating the Record

Lots of things happened since I last cared to blog. Here's the first one, chronologically. While I'm trying to not get too excited about Dependency Injection, I realized that it's about time to use some services in Inka, and I sort of needed an IoC container. My first idea was to use StructureMap by Jeremy Miller, but I wanted an automocking container for my tests as well, and I was forced to choose Castle Windsor. Or so I thought at that time -- it turned out that, first, I didn't have time to implement these things, and second, StructureMap also has an automocking container. But that's not the point.

The point is, I thought, hell, why not move to a decent ORM as well? Meaning Castle ActiveRecord.

My first ORM was Neo (Net Entity Objects, I think the name has been invented before The Matrix). Unlike Castle AR, which is really a nice interface for NHibernate, Neo is Active Record in a true sense. I'd even say, it's Typed DataSets Done Right. Each entity class is a wrapper over the DataRow class, so we can track the state (is it added, deleted, or modified) automatically. Each object is created via a factory, and added to the context automatically, so it can appear in your query results without being saved to a database. Cool. And also very convenient for testing.

Speaking of testing, I still can't figure out how to test AR applications without a database connection.

So, Neo is not for purists (nor is AR), they want POCOs. But I''m just a script kiddie, so it's OK for me. Neo's got a code generator, so I quickly enjoyed the idea and put all sorts of stuff into my templates, including some UI-related things. Don't blame me, I haven't heard about SoC in these days. In return, I managed to do some boilerplate stuff extra quickly.

Unfortunately, there's a very small community around Neo, and the development seems to stop. I've fixed a few bugs, but never got to publishing the fixes (only recently I learned that I had violated the LPGL license). Also, it's pretty simple, and perhaps won't cover more complex situations, including fat query results.

I'm only learning Active Record, and it has some very nice points (including a great graphic schema editor, Active Writer), but I've had some really weird moments with it. One, for example, is that I've been getting some weird exceptions that I wasn't able to reproduce in my tests. Almost intuitively, I invoked a Scope constructor at startup, and the problem was gone, although I never used the created scope variable anywhere! Turned out there's some dirty game with shared (static in C# :) variables here and there. I used to wonder why people hate these statics, now I know!

I'd like to learn more about AR, but now I'm totally thrilled by Ivonna being released soon, so I'm leaving the applied programming world for a while..

Thursday, 21 February 2008

Just can't help it!

Well, finally I'm at the point where I have to prepare an installer for Ivonna. Actually, I'm stuck at this point for about two weeks. Not because of installer itself, but because of the documentation. No, I diligently wrote all the xml comments, believe me. But these comments have to make way into a nice docs, preferably with some custom ("conceptual" in Sandcastle terms) content. And it has to be in Html Help 2.x format. This morning I finally discovered that my help can be viewed in the Document explorer. Was it because of the electricity been cut for a moment?

Anyway, here's my recipe. I'll be using VS 2005 with the 2007 SDK, Sandcastle January 2008 release, and DocProject 1.10.0 RC.
  1. Install the VS SDK.
  2. Install Sandcastle. It is important to install it after the SDK, because the SDK contains its own version of Sandcastle, which is too old. Anyway, if you installed Sandcastle first accidentally, just edit your DXROOT environment variable so that it points to the Sandcastle's folder.
  3. Download and extract the presentation file patches. This fixes a bug in Sandcastle when you don't have a root topic.
  4. Install DocProject. A major annoyance is a bug in the 1.10 RC version: the Add-in looks for the Project toolbar that has more than 40 items. Mine had only 38. The suggested workaround is to download the code and patch it manually, but I couldn't afford that, so, instead I wrote a macro that added 3 ugly entries to my Project menu. Anybody interested? Update: there's a 1.10.1 version that supposedly fixed this bug.
  5. Add a DocProject to your solution. You can add a DocSite project instead if you need online help (it will build the offline help as well).
  6. You might want to modify some templates offered by the DocProject wizard. But don't remove the feedback section entirely -- you'll get a JavaScript error later.
  7. Modify the AssemblyInfo.cs file to reflect your documentation title and organization.
  8. Don't ever rebuild the project -- it'll lose some essential files.
  9. In the project wizard or later in the DocProject properties (available as an additional context menu item for your project), select "build html 2.0".
  10. You might want to select the fastest build option -- choose "none" in the "build assembler options".
  11. Now, after building the project, you have an HxS file in your output folder.
  12. Unlike a chm file, you can't open it directly. Instead, you have to register it and navigate to it using the special ms-help protocol.
  13. So, how do you register it?
  14. The manual steps recommended copying a certain merge module from the sdk directory and manually editing it with Orca.
  15. Like I was going to do that.
  16. Actually I even did a first step.
  17. But it turned out that there's a new kind of extensibility projects called "Help Integration Wizard". What you want is create a merge module that you'll add to your installation project. The wizard is pretty trivial, and it stores all its settings in the CollectionFiles folder, so you can edit them manually later.
  18. At the first step, I chose the help file that I discovered in the Help/bin folder of my DocSite project.
  19. The wizard showed me the structure of my future help. It contained one topic, called "sample topic" or something. it was on the left pane, and on the right there were my topics, conveniently excluded from the future help. I renamed the sample topic to "Ivonna", and added my topics under that root topic.
  20. I also chose the namespace for my help -- ivonna.docs.
  21. However, building the project didn't work for me, the postbuild event (the one that actually edited the merge module) failed. Invoking it manually from the command line failed as well. So, I opened the executable in the Reflector, and it turned out that it has been designed specifically in order to hide the possible cause of an error. For example, the exception was coming from a logging statement that was inside a catch block, so it was cleverly hiding the real exception. Also, all exception messages came from some native calls, so, for example, "File not found" turned into "Invalid handle".
  22. So, I just wrote something that mimicked this app, and it somehow worked. Now I have a patched merge module that I can add to my installer. Update: It turned out that the problem was with the path -- it contained cyrillic characters. Once I created another project in a different location, everything went smooth.
  23. Now, you should add the merge module from this project to your setup. You should probably create a Help folder under your app folder, and choose it for the merge module files. Choose the merge module in your setup project, and choose the folder in the Properties, KeyOutput -> MergeModuleProperties -> Module Retargetable Folder.
  24. By the way, you can see all installed help namespaces via a handy utility located here: \Program Files\Visual Studio 2005 SDK\2007.02\VisualStudioIntegration\Archive\HelpIntegration\Unsupported\Namespace.exe
  25. You probably want to include a shortcut to your documentation. It should be something like this: "%CommonProgramFiles%\Microsoft Shared\Help 8\dexplore.exe" /helpcol ms-help://ivonna.docs. Note that I put my namespace after the "ms-help://" stuff. Yes, you are even able to view it in IE.
  26. After you successfully made the installer, you discover that when the documentation opens it shows an empty page. If you want an introduction or something to appear, you should register it as "DefaultPage". Details can be found here.
  27. Modify your shortcut like this: "%CommonProgramFiles%\Microsoft Shared\Help 8\dexplore.exe" /helpcol ms-help://ivonna.docs /LaunchNamedUrlTopic DefaultPage.
By the way, you can download Ivonna from my new site here, and view the online docs (produced with DocProject) here. I'll be blogging about Ivonna on my site's blog.