Wednesday, 17 December 2008

Web Application Testing In .NET

Recently I had the opportunity to use the open source software tool, WatiN (Web Application Testing in .NET), in a commercial environment. It is inspired by the similar Watir (Web Application Testing in Ruby). Initially (about a couple of years ago) I dabbled in Watir after being alerted to its existence by my friend, Mark Hudson. Also, it gave me an excuse to play around with Ruby a bit, Ruby being the sleek new kid on the block at the time.

As it happens, although WatiN and Watir are web testing frameworks,  they can be used for automating browser operations - text entry, mouse clicks, navigation, etc., independently of any testing. So you could use them to log into Internet banking sites. Well, that's becoming increasingly impossible with the rise of two-factor authentication schemes, such as Barclays' PINsentry system, that additionally use card readers.

What else do we need for testing our web applications with WatiN?

First, we should note that WatiN is for testing the UI as such, not for testing the application as a whole. Depending on how the web application is structured we can use standard test-driven development for the rest of the application.

The other tools I used in combination with WatiN were:

WatiN Test Recorder is helpful for generating rough starter code from your mouse clicks and keystrokes and giving you a feel for how to use the WatiN API.

The IE Developer Toolbar is useful for helping you identify the HTML control identifiers. (Currently WatiN requires Internet Explorer but Firefox support is in beta.)

NUnit is the framework for running the tests in C# or VB .NET.

WatiN itself has recently been upgraded to include .NET 3.5 and C# 3.0  language features - LINQ and lambda expressions. It has an excellent and active mailing list for technical support.

I will discuss WatiN in more detail in a subsequent post.

Friday, 12 December 2008

Taming the "Broken" IDisposable Implementation for WCF Clients

One of the early lessons we learn in Microsoft .NET development is to always use the using statement on objects that implement the IDisposable interface.  The effect is that the Dispose() method is called at the end of the using statement to ensure that resources are cleaned up.

Unfortunately, this pattern fails for Windows Communication Foundation (WCF) clients for the reason that is explained quite well in the posts, WCF Clients and the "Broken" IDisposable Implementation by David Barrett and Indisposable: WCF Gotcha #1 by Jesse Ezell. A Microsoft WCF development team member, Brian McNamara, explained the thinking behind this design decision in a post at the MSDN WCF forum Why does ClientBase Dispose need to throw on faulted state?

David and Jesse provide some quite elegant workarounds to this problem. I have adapted their solutions and applied them to a simple WCF service to illustrate their use. Let's take a step by step approach.

Imagine we have a simple WCF Calculator service.  This contains a single operation that adds two integers and returns the result.

Accessing the Calculator Service the Wrong Way

We can access the Calculator service using either a ChannelFactory<T> or a client proxy (a generated ClientBase<T> derived class). In that case, the client code might look like this.

private const string Uri = 
"net.pipe://localhost/CalculatorService/Calculator";

private static void RunUsingChannelFactoryWithUsingStatement()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress ep = new EndpointAddress(Uri);

ICalculator channel =
ChannelFactory<ICalculator>.CreateChannel(
binding, ep
);

using (channel as IDisposable)
{
int result = channel.Add(1, 2);
Console.WriteLine(result);
}
}

private static void RunUsingProxyWithUsingStatement()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpoint = new EndpointAddress(Uri);

using (
CalculatorClient client =
new CalculatorClient(binding, endpoint)
)
{
int result = client.Add(1, 2);
Console.WriteLine(result);
}
}

However,  we should not do this since Dispose() calls Close() and Close() can throw an exception.  Instead we have to be more long-winded.


Accessing the Calculator Service the Correct Way

private static void RunUsingChannelFactoryWithTryFinally()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpoint = new EndpointAddress(Uri);
ChannelFactory<ICalculator> channelFactory =
new ChannelFactory<ICalculator>(binding, endpoint);

IClientChannel channel =
channelFactory.CreateChannel() as IClientChannel;

bool closed = false;
try
{
int result = (channel as ICalculator).Add(1, 2);
Console.WriteLine(result);
channel.Close();
closed = true;
}
finally
{
if (!closed)
{
channel.Abort();
}
}
}

private static void RunUsingProxyWithTryFinally()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpoint = new EndpointAddress(Uri);
ClientBase<ICalculator> client =
new CalculatorClient(binding, endpoint);

bool closed = false;
try
{
int result = (client as CalculatorClient).Add(1, 2);
Console.WriteLine(result);
client.Close();
closed = true;
}
finally
{
if (!closed)
{
client.Abort();
}
}
}

Can we improve on this? Yes.


Improved Solution 1 - Implement IDisposable in Proxy Partial Class

public partial class CalculatorClient : IDisposable
{
void IDisposable.Dispose()
{
try
{
Close();
}
catch (CommunicationException)
{
Abort();
}
catch (TimeoutException)
{
Abort();
}
}
}

With this in place we can force the using statement to do the right thing. This is now valid.

using (CalculatorClient client = new CalculatorClient(binding, endpoint))
{
int result = client.Add(1, 2);
Console.WriteLine(result);
}

This is an elegant solution but it has two disadvantages.



  1. It only works for proxy-based client access, not ChannelFactory-based access.
  2. The client must write the partial class for each new service they wish to access (though this could be automated with code generation).

Improved Solution 2 - Use Generics and Delegates


First we define these two delegates that represent code blocks for proxy-based and ChannelFactory-based client access respectively.

/// <summary>
///
Represents a code block containing WCF proxy client method calls.
/// </summary>
public delegate void
UseProxyDelegate<TInterface, TClass>(TClass proxy);
/// <summary>
///
Represents a code block containing WCF client method calls
/// using a <see cref="ChannelFactory"/>.
/// </summary>
public delegate void
UseChannelFactoryDelegate<TInterface>(TInterface channel);

Then we define a class that abstracts away the closing of the connection for us.

public static class Service
{
/// <summary>
///
Uses a WCF client via its proxy.
/// </summary>
public static void UseProxy<TInterface, TClass>(
ClientBase<TInterface> proxy,
UseProxyDelegate<TInterface, TClass> codeBlock
)
where TInterface : class
where
TClass : ClientBase<TInterface>, TInterface
{
if (proxy == null)
throw new ArgumentNullException(
"proxy", "proxy is null."
);
if (codeBlock == null)
throw new ArgumentNullException(
"codeBlock", "codeBlock is null."
);

bool closed = false;
try
{
codeBlock(proxy as TClass);
proxy.Close();
closed = true;
}
finally
{
if (!closed)
{
proxy.Abort();
}
}
}

/// <summary>
///
Uses a WCF client via its <see cref="ChannelFactory"/>.
/// </summary>
public static void UseChannelFactory<TInterface>(
ChannelFactory<TInterface> channelFactory,
UseChannelFactoryDelegate<TInterface> codeBlock
)
where TInterface : class
{
if (channelFactory == null)
throw new ArgumentNullException(
"channelFactory", "channelFactory is null."
);
if (codeBlock == null)
throw new ArgumentNullException(
"codeBlock", "codeBlock is null."
);

IClientChannel channel =
channelFactory.CreateChannel() as IClientChannel;
bool closed = false;
try
{
codeBlock(channel as TInterface);
channel.Close();
closed = true;
}
finally
{
if (!closed)
{
channel.Abort();
}
}
}
}

With this in place we can now proceed to the final solution.

private static void RunUsingChannelFactory()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpoint = new EndpointAddress(Uri);
ChannelFactory<ICalculator> channelFactory =
new ChannelFactory<ICalculator>(binding, endpoint);

Service.UseChannelFactory<ICalculator>(
channelFactory,
delegate(ICalculator calculator)
{
int result = calculator.Add(1, 2);
Console.WriteLine(result);
}
);
}

private static void RunUsingProxy()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpoint = new EndpointAddress(Uri);
ClientBase<ICalculator> client =
new CalculatorClient(binding, endpoint);

Service.UseProxy<ICalculator, CalculatorClient>(
client,
delegate(CalculatorClient calculator)
{
int result = calculator.Add(1, 2);
Console.WriteLine(result);
}
);
}

If we are using C# 3.0 we can replace the anonymous delegates with statement lambdas but there is no real gain in expressiveness in doing so in this case.


This final solution is more general purpose but is straightforward to use.

Thursday, 11 December 2008

Who's the worst programmer on your team?

This is the question Esther Schindler asks here. She concludes that it is quite difficult to answer this using metrics but that developers generally know who the poor ones are. I've often thought that attitude matters more than ability. Many of the answers she lists are at root down to attitude.  I shall offer a few words on a couple of  the points.

Do not offer or ask for help

It is surprising how many developers do not offer help in particular. Now, it's OK if they are busy. I've often been in situations where I've asked a question and been told that the developer was busy. But then the good ones do eventually attend to your problem when the opportunity arises. The bad ones either ignore you or provide a monosyllabic answer.

Now, of course you can also encounter developers who ask questions about everything including about things they could easily look up in the online help or Google for. We all ask the occasional question of this sort through laziness but the emphasis should be on occasional.

Are arrogant

In the post this is taken to mean the "know-it-all" developer who is always right. In the past I've watched some of these characters make themselves look absurd simply by their refusal to admit that they made a mistake. Get over it. We're all human.

Wednesday, 10 December 2008

Introduction

I am an independent software consultant who lives in the UK. I shall be publishing occasional musings on various technical topics when it takes my fancy.