CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Karl Seguin

developer @ Fuel Industries ottawa, ontario

January 2007 - Posts

  • HttpHandler Unit Testing

    The pains of unit testing the front-end portion of ASP.NET far outweigh the benefits. It's easier and more worthwhile to stick with your domain and data layers.

    Lately, I've been working on a project that makes heavy use of HttpHandlers. A significant amount of code has nothing to do with classes in the System.Web.UI namespace - so no controls, no viewstate, no postback. Since those are all the things that make unit testing ASP.NET a nasty affair, I figured it might be worthwhile to try and unit test the code in question.

    It ended up being really easy, and although I've built a mini-base test class for my test fixtures to inherit, it really all comes down to 1 very simple method:
     

    protected string RawRequest(string fileName, string queryString)
    {
       StringBuilder output = new StringBuilder();
       using (StringWriter sw = new StringWriter(output))
       {
          HttpResponse response = new HttpResponse(sw);
          HttpRequest request = new HttpRequest(fileName, "http://fueltest.net/" + fileName, queryString);
          HttpContext context = new HttpContext(request, response);
          new RequestHandler().ProcessRequest(context);
       }
       return output.ToString();
    }

    where RequestHandler() is my core HttpHandler.

    My tests are able to validate that the output, given the querystring inputs, are correct.
     

    Posted Jan 25 2007, 10:16 AM by karl with 4 comment(s)
    Filed under:
  • Page_Load is NOT evil

    There's a post over at SitePoint calling Page_Load evil. The complaint is pretty much that because it's automatically there (VS.NET generates the Page_Load handler by default) people just cram everything in there.

    Technically some broad statements about Page_Load are made which I don't agree with. Since when is Page_Load too late to add controls? Page_Load is too early to deal with button clicks? You can only have 1 event at a time, it's either going to be before or after...events are fired AFTER Page_Load so that you can dynamically re-add controls and have their event raised.

    Overall, I agree that a lot of people abuse Page_Load. They never understand why using PreRender, or Init might be much better alternatives given a particular case. ASP.NET is hard - I've even argued that it's too hard.

    In this case though, the blame squarely goes to the overwhelming amount of bad programmers. If you're doing ASP.NET you simply MUST understand the page lifecycle, even though it's relatively super-complex. Take 1 day, load up Reflector, examine the Page class (specifically the ProcessMain function), load up some tests, play with viewstate, dymamic controls, events and prerender and become a better programmer. You're either an ASP.NET programmer or you aren't.

    The article proposes a more comprehensive alternative to just Page_Load. All he's really doing is renaming events, Init to AddDynamicControls, PreInit to ChooseMasterPage, Load to LoadData and PreRender to BindData. Those might be good guidelines for someone just starting to learn ASP.NET, but it doesn't really do anything to promote an understanding of and proper use of the page lifecycle.

  • Fast string to integer conversion

    I'm dealing with string-based inputs that actually represent integers. The system is under very tight performance constraints - tens of thousands of users on it at the same time with frequent server interaction (i.e., they aren't spending 10 minutes reading a page).

    The hubris starts to kick in and I tell myself "I can write this faster!". I open up Reflector to scope out my competition and see that Int32.Parse looks pretty heavy..I mean, just look at ParseNumber.ParseNumber.

    The first thing I do is write a simple load tester. It looks something like:

    string[] testString = {... 1000 randomly generated string values representing VALID positive integers ...}
    DateTime startTime = DateTime.Now;       
    for (int j = 0; j < 100; ++j)
    {
        for (int i = 0; i < testString.Length; ++i)
        {               
          Int32.Parse(testString[ i ]);
        }
    }   
    Console.WriteLine(DateTime.Now.Subtract(startTime).TotalMilliseconds);

     
    That's 100 000 iterations. It takes....33 milliseconds. Let's be honest, my performance requirements are tight, but not nearly that tight. I can probably tweak 1 index and get 50x the performance boost from it.

    But...curiosity has the best of me. So I see what I can do. Here's my first attempt:

    internal static int AsciiToInteger(string stringToConvert)
    {
       int zeroValue = '0';
       char[] characters = stringToConvert.ToCharArray();        
       int value = 0;
       for (int i = 0; i < characters.Length; ++i)
       {
          char c = characters[ i ];
          if (c < '0' || c > '9')
          {
             throw new FormatException("Input string was not in the correct format");
          }
          value = 10 * value + (c - zeroValue);
       }
       return value;
    }

    It runs in 11 milliseconds...not bad.  Using unsafe code I manage to cut that down to 5milliseconds:

    public unsafe static int MyParse2(string stringToConvert)
    {
        int value = 0;       
        int length = stringToConvert.Length;
        fixed(char* characters = stringToConvert)
        {
            for (int i = 0; i < length; ++i)
            {                   
                value = 10 * value + (characters[ i ] - 48);
            }   
        }
        return value;
    }

    I'll revisit the issue again once I profile the app and see if there's any benefits to the overall system by moving to something other than Int32.Parse, but thought I'd share my experience.

    Oh, and incase anyone's wondering, I did write some unit tests for it since I do realize that my version is far less robust:

    [Test, ExpectedException(typeof(FormatException))]
    public void AtoIDoesntAcceptNegatives()
    {
       AsciiToInteger("-32235");
    }
    [Test, ExpectedException(typeof(FormatException))]
    public void AtoIDoesntAcceptNonNumericCharacters()
    {
       AsciiToInteger("3d2a35");
    }
    [Test, ExpectedException(typeof(NullReferenceException))]
    public void AtoIThrowsExceptionWhenNull()
    {
       AsciiToInteger(null);
    }
    [Test]
    public void AtoIReturnsZeroForEmptyString()
    {
       Assert.AreEqual(0, AsciiToInteger(""));
    }     
    [Test]
    public void AtoIReturnsProperInteger()
    {        
       Assert.AreEqual(Int32.MaxValue, AsciiToInteger(Int32.MaxValue.ToString()));
       Assert.AreEqual(0, AsciiToInteger("0"));
       Assert.AreEqual(43, AsciiToInteger("43"));
       Assert.AreEqual(54354, AsciiToInteger("054354"));
    }

More Posts