Want to show your appreciation?
Please a cup of tea.
Showing posts with label Utility. Show all posts
Showing posts with label Utility. Show all posts

Wednesday, November 04, 2009

Unit Test Value Object – Reusable Test Fixture

To Test Or Not Test

Shall I unit test value object? Here are some arguments that I found when googling for an answer to the question.

  1. Value object are so simple that you don’t need to test them.
  2. Test value object is tedious, basically copy and pasting and you can make the same mistake that you did in the value object itself.
  3. If you test every other part of the application, you would have tested your value object.

And there are also opposite opinions.

  1. Value objects are not as simple as you thought. How about initial property value, equals, hashcode and cloneable? (Yes, that reminds me that I made quite some mistakes in value objects)
  2. Unit test is tedious in nature. Tedious is not a reason for not doing it. But it only means that we need to find a better way.
  3. This is about unit testing, not integration testing. Value object is a unit that should be tested alone.

In summary, value objects are relatively simple so that the cost of manually writing test cases for them are too high to justify the benefit but not testing them leaving potential bugs.

The Reusable Test Fixture

Compromising the quality is a no in my book so the solution is to develop reusable tests to replace the repetitive and tedious work. This is what programmers are good at after all!

Let’s take a look at a very simple value object.

public class Foo
{
    public int Id { get; set; }
 
    public string StringProperty { get; set; }
 
    public long LongProperty { get; set; }
}

Writing a unit test for this class manually is plain boring. I should be able just do it in one line with a reusable test fixture.

[TestFixture] public void FooTest : SomeMagicReusableTestFixture<Foo> {}

This magic reusable test fixture is called NewableValueObjectTestFixture. It sets each property with a few (three minimal) values and read it back to make sure it gets the same value previously set. Of course, it does much more then just that…

Default Property Value

The above example is probably too simple. What if my property has default value? say the Id property is initialized to –1. No problem! The reusable test fixture also verifies the initial value of properties too. It expects all the properties are initialized to the default value of the type, or a value specified by DefaultValueAttribute.

The code below will fail the test because the the default value for int type is 0, not –1.

    private int _id = -1;
 
    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }

If you intended to initialized to –1, you should tell this to the test fixture. The information is also useful to other .Net tools.

    private int _id = -1;
 
    [DefaultValue(-1)]
    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }

The code above passes the test happily.

Equals and GetHashCode

NewableValueObjectTestFixture and its brother ValueObjectTestFixture test the Equals and GetHashCode methods too. They basically ensure that:

  1. The value object does not equals to null.
  2. The value object does not equals an arbitrary object (new object()).
  3. The value object equals to itself.
  4. The value objects are not equal if any one of their properties are not equal.
  5. When two value objects have same property values and Equals method returns true. The GetHashCode methods must return same value.

The implementation in the Object class complies to the above rules so the your value object passes the tests by default. But if you decide to override any of them, the test helps to ensure you have implemented Equals and GetHashCode properly, otherwise the test will fail. Below is generated by Resharper and it passes the test.

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof(Foo)) return false;
        return Equals((Foo)obj);
    }
 
    public bool Equals(Foo other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return other._id == _id &&
            Equals(other.StringProperty, StringProperty) &&
            other.LongProperty == LongProperty;
    }
 
    public override int GetHashCode()
    {
        unchecked
        {
            int result = _id;
            result = (result * 397) ^ (StringProperty != null ? StringProperty.GetHashCode() : 0);
            result = (result * 397) ^ LongProperty.GetHashCode();
            return result;
        }
    }

A few days later if I add another property to the Foo class and forget to added it to the Equals method, the test fixture will effectively catch it and fails the test. This is exactly what I wanted, how many times did I forget this?

Cloneable

If my value object implements ICloneable, the test fixture will help to check that as well. It clones the object and test all readable properties to make sure the cloned one has the same property values. Below is my typical clone implementation and it works well with the test fixture.

public class Foo : ICloneable
{
    public int Id { get; set; }
 
    public string StringProperty { get; set; }
 
    public long LongProperty { get; set; }
 
    object ICloneable.Clone()
    {
        return Clone();
    }
 
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}

Type of Property

Since the test fixture needs to generate test data, it had limited support for the type of the properties. For example, a property of interface type would be difficult for ValueObjectTestFixture to create test data of it.

Although, out of the box, ValueObjectTestFixture only support properties of c# build-in data types plus DateTime and TimeSpan, but it provides two extension mechanisms. You can either use a mock framework to generate mock objects for interface type and class type, or override the TestData method to provide your own test data for types that are not supported by the default implementation. Of course, nothing prevents you from making mixed use of both mechanisms.

Future enhancement may have ValueObjectTestFixture support enum, concrete class and/or maybe struct.

Using Mock Framework to Generate Test Data

ValueObjectTestFixture accepts an IMockTestDataProvider instance. I have included an implementation using RhinoMocks. But you can easily do the same using any other mock frameworks. RhinoMockTestDataProvider mocks any interfaces, or classes that are not sealed and has a default constructor. We may be able to remove the requirement of a default constructor in future but for now that is it. :)

Take a look at below value object:

public class ValueObject
{
    public IList<string> StringIList { get; set; }
 
    public List<TimeSpan> TimeSpanList { get; set; }
 
    public ICollection<int> IntICollection { get; set; }
 
    public IDictionary<string, object> StringObjectIDictrionary { get; set; }
 
    public Dictionary<int, DateTime> IntDataTimeDictionary { get; set; }
 
    public Component Component { get; set; }
 
    // other properties, equals, hash code, clone and etc.
}

To test this, the test class is as simple as below:

    [TestFixture]
    public class MockProviderTest : NewableValueObjectTestFixture<ValueObject>
    {
        public MockProviderTest()
        {
            MockProvider = new RhinoMockTestDataProvider();
        }
    }

The complete example can be found in Google code: MockProviderTest.cs

Provide Your Own Test Data

If you have property type that is neither supported out-of-box, nor by a mock framework. You can always provide it yourself by overriding the method TestData. Below is an example from CustomTest.cs:

        protected override IEnumerable TestData(PropertyInfo property)
        {
            // ValueObjectTestFixture doesn't support enum at this moment
            // so we have to provide our own test data.
            if (property.PropertyType == typeof(Operation))
            {
                return new[]
                           {
                               Operation.Subtraction, 
                               new Operation(), 
                               Operation.Addition, 
                               Operation.Muiplication, 
                               Operation.Division
                           };
            }
            return base.TestData(property);
        }

Extensibility

While ValueObjectTestFixture makes simple things simpler, it ensures complex things possible too. Here are some additional features that will come handy when testing not so simple value objects.

Value Object without Default Constructor

What if the value object doesn’t have a default constructor? No problem, have the test case inherit from ValueObjectTestFixture. The only difference between ValueObjectTestFixture and NewableValueObjectTestFixture is that the former forces you to override an abstract method: NewValueObject, which you are required to return a new instance of the value object under test. Below example is from CustomTest.cs:

        protected override CustomObject NewValueObject()
        {
            return new CustomObject("TestObject");
        }

Test Special Properties Myself

There are times that value object has non-simple properties. For example, when a read only property is initialized by the constructor:

public class CustomObject
{
    private readonly string _name;
    public CustomObject(string name)
    {
        _name = name;
    }
 
    public string Name { get { return _name; } }
}

Or, when a property is calculated from others:

public int Result
{
    get
    {
        switch (Operation)
        {
            case Operation.Addition:
                return LeftOperand + RightOperand;
            case Operation.Subtraction:
                return LeftOperand - RightOperand;
            case Operation.Muiplication:
                return LeftOperand * RightOperand;
            case Operation.Division:
                return LeftOperand / RightOperand;
            default:
                throw new InvalidOperationException("Unsupported operation " + Operation);
        }
    }
}

ValueObjectTestFixture provides two ways to exclude those properties from being tested by it. But then you need to write the test cases yourself.

One way is to use one of the ExcludeProperties methods. Typically you call it in the constructor of your test fixture. Below is from CustomText.cs:

    [TestFixture]
    public class CustomTest : ValueObjectTestFixture<CustomTest.CustomObject>
    {
        public CustomTest()
        {
            // Exclude the Name property from being tested by ValueObjectTestFixture.
            ExcludeProperties("Name");
        }
        // ... ...
    }

This completely excludes the property from all the tests administered by ValueObjectTestFixture.

Another less aggressive approach is to override one of the XyzCandidates methods. They are listed below to provide a custom list of properties for the corresponding tests:

Example from CustomText.cs:

        public override IEnumerable<PropertyInfo> EqualsTestCandidates()
        {
            // Exclude the Tag property from equality test.
            return from p in base.EqualsTestCandidates() where p.Name != "Tag" select p;
        }

You opinions is important. Let me know what you think :)

Thursday, July 09, 2009

Some Utilities Extend Rhino.Mocks

I have recently blogged a few posts about how to let Rhino Mocks do more for us. They are listed below:

All those features require reader either change the source code of Rhino Mocks or download a special version of Rhino Mocks that I built. Those changes are quite invasive.

By carefully looking at the code, I found that there are some features can be extract into an extension library without touching the Rhino Mocks itself. Actually, only the ordered expectation requires source code change. Hence, I decided to build a extension library so that everybody can use. You can either drop the source into your test project or download the Rhino.Mocks.Extension.dll and add the reference to it.

Here is the current feature list:

  1. Create multi mock for AAA: Mockery.GenerateMultiMock
  2. Create partial mock for AAA: Mockery.GeneratePartialMock, Mocker.GeneratePartialMultiMock
  3. Ability to use test framework's assert: Assert.IsTrue(mock.ActivityOf(x=>x.Foo()));
  4. Use operator to check "or" condition: mock.ActivityOf(x=>x.Foo()) | mock2.ActivityOf(x=>x.Bar())
  5. Support assert for one and exactly one is called: Mockery.ExactOneOf(m.ActivityOf(...), ...).AssertOccured;

Please see my earlier posts for the detail of feature 3-5.

Wednesday, July 08, 2009

Introduce A Powerful AAA Syntax for Rhino.Mocks

I was again (last one was about ordered expectations for AAA) inspired by a question posted on the Rhino Mocks support email group, expect a call to any one of overloaded method. How do we do that? I have proposed a solution and that was pretty much what I did before. When we talk about TDD, we write the test before implementation. And sometimes, designer writes the test cases for somebody else to complete the implementation.

In many cases, all we care is that one of the send methods of the notification service is called. Which exact send method to call is all up to the implementation.

The Syntax

There got to be a better why to do this! The same may not necessary only apply to overloaded methods. For any methods, I should be able to just write something like this:

    ma.AssertWasCalled(a=>a.Foo()) or mb.AssertWasCalled(b=>b.Bar());

The experience gained from my work on the ordered expectation tells me that this is very possible. All I need to figure out is the syntax for this. May be

    ma.AssertWasCalled(a=>a.Foo()).Or(mb.AssertWasCalled(b=>b.Bar()));

or even better to use operator overload:

    ma.AssertWasCalled(a=>a.Foo()) | mb.AssertWasCalled(b=>b.Bar());

Well, the problem is that AssertWasCalled fails right away when a.Foo() is not called. Why do we have to fail it? Why cannot we leave it to the test framework to do the assert? But anyway, in order to not break the existing contract of AssertWasCalled, we'll have to use something different. How about this?

    Assert.IsTrue(ma.ActivityOf(a=>a.Foo()) || mb.ActivityOf(b=>b.Bar()));

Have more methods, not problem!

    Assert.IsTrue(foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything)) || 
        foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything, Arg<Another>.Is.NotNull)) || 
        foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything, Arg<Another>.Is.NotNull, Arg<int>Is.Anything)));

This syntax give us a lot of flexibility. I can use operator overload for ordered expectation as well.

    Assert.IsTrue(ma.ActivityOf(a=>a.Foo()) < mb.ActivityOf(b=>b.Bar()));

And there are unlimited possibilities, some examples below:

    // chained ordering
(m.ActivityOf(...) > m.ActivityOf(...) > m.ActivityOf(...)).AssertOccured;
    // mixed or ordering
(m.ActivityOf(...) | m.ActivityOf(...)) < m.ActivityOf(...)).AssertOccured
    var sent = (m.ActivityOf(a=>a.Sent(x)) | m.ActivityOf(a=>a.Sent(x, y)));
    Assert.IsTrue(sent); // assert sent is called
    Assert.IsTrue(sent < m.ActivityOf(a=>a.Close())); // assert sent called before close

Implementation

This is exciting, isn't it? How difficult to implement this? I give it a try by building on top of the changes that I made for the ordered expectation in the previous post. It turn out to be quite easy. Although the code below is no where close to perfect, and indeed it is quick and dirty to some extend, but it served well as proof of concept to introduce this technique.

Here we go the code. The Activities class is the core. It provides implementation for all the operator overload.

    public class Activities : IComparable<Activities>
    {
        private static readonly CallRecord[] _emptyRecords = new CallRecord[0];
        private readonly IList<CallRecord> _callRecords = _emptyRecords;
        private readonly ExpectationViolationException _exception;

        public Activities(IList<CallRecord> callRecords)
        {
            if (callRecords == null || callRecords.Count == 0)
                throw new ArgumentException(
                    "Must not be null or empty.", "callRecords");
            _callRecords = callRecords;
        }

        public Activities(ExpectationViolationException exception)
        {
            if(exception==null) throw new ArgumentNullException("exception");
            _exception = exception;
        }

        public bool Occured
        {
            get { return _callRecords.Count > 0; }
        }

        public Activities OccuredBefore(Activities other)
        {
            if (!Occured) return this;
            if (!other.Occured) return other;

            var thisLast = GetLast();
            CallRecord otherFirst = other._callRecords[0];

            return thisLast.Sequence < otherFirst.Sequence ? other
                : new Activities(NewOrderException(thisLast, otherFirst));
        }

        private ExpectationViolationException NewOrderException(
            CallRecord before, CallRecord after)
        {
            return new ExpectationViolationException(
                "Expected that call " + before.Method +
                " occurs before call " + after.Method +
                ", but the expectation is not satisfied.");
        }

        public Activities OccuredAfter(Activities other)
        {
            if (!Occured) return this;
            if (!other.Occured) return other;

            CallRecord otherLast = other.GetLast();
            CallRecord thisFirst = _callRecords[0];
            return otherLast.Sequence < thisFirst.Sequence ? other
                : new Activities(NewOrderException(otherLast, thisFirst));
        }

        public Activities Or(Activities other)
        {
            if (Occured) return this;
            if (other.Occured) return other;
            return new Activities(new ExpectationViolationException(
                this._exception.Message + "\nor\n" + other._exception.Message));
        }

        public Activities First {
            get {
                return Occured ? new Activities(new CallRecord[] {_callRecords[0]}) : this;
            }
        }

        public Activities Last {
            get {
                return Occured ? new Activities(new CallRecord[] { GetLast() }) : this;
            }
        }

        private CallRecord GetLast()
        {
            return _callRecords[_callRecords.Count - 1];
        }

        public int CompareTo(Activities other)
        {
            if (ReferenceEquals(this, other)) return 0;
            return OccuredBefore(other) ? -1 : 1;
        }

        public static implicit operator bool(Activities activities)
        {
            return activities.Occured;
        }

        public static Activities operator <(Activities a1, Activities a2)
        {
            return a1.OccuredBefore(a2);
        }

        public static Activities operator >(Activities a1, Activities a2)
        {
            return a1.OccuredAfter(a2);
        }

        public static Activities operator |(Activities a1, Activities a2)
        {
            return a1.Or(a2);
        }

        public static Activities operator ^(Activities a1, Activities a2)
        {
            return OneOf(a1, a2);
        }

        public static bool operator true(Activities a)
        {
            return a.Occured;
        }

        public static bool operator false(Activities a)
        {
            return !a.Occured;
        }

        public void AssertOccured()
        {
            if (_exception != null) throw _exception;
        }

        internal static Activities ExactOneOf(params Activities[] activitiesList)
        {
            Activities one = null;

            foreach (var activities in activitiesList)
            {
                if (!activities.Occured) continue;
                if (one == null) one = activities;
                else
                    return new Activities(
                        new ExpectationViolationException(
                            "Both " + one._callRecords[0].Method +
                            " and " + activities._callRecords[0].Method +
                            " was called"));
            }
            if (one != null) return one;

            StringBuilder sb = new StringBuilder("None of below is satisfied:");
            foreach (var activities in activitiesList)
            {
                sb.Append('\n').Append(activities._exception.Message);
            }
            return new Activities(new ExpectationViolationException(sb.ToString()));
        }
    }

The other class holds a few extension methods that glues this new API to existing Rhino Mocks.

    public static class Mockery
    {
        public static Activities ActivityOf<T>(this T mock, Action<T> action, 
            Action<IMethodOptions<object>> setupConstraints)
        {
            try
            {
                return new Activities(mock.AssertWasCalled(action, setupConstraints));
            }
            catch (ExpectationViolationException e)
            {
                return new Activities(e);
            }
        }

        public static Activities ActivityOf<T>(this T mock, Action<T> action)
        {
            return ActivityOf(mock, action, DefaultConstraintSetup);
        }

        public static Activities ActivityOf<T>(this T mock, Function<T, object> func, 
            Action<IMethodOptions<object>> setupConstraints)
        {
            return ActivityOf(mock, new Action<T>(t => func(t)), setupConstraints);
        }

        public static Activities ActivityOf<T>(this T mock, Function<T, object> func)
        {
            return ActivityOf(mock, func, DefaultConstraintSetup);
        }

        public static void Assert(Activities activities)
        {
            activities.AssertOccured();
        }

        public static Activities ExactOneOf(params Activities[] activitiesList)
        {
            return Activities.ExactOneOf(activitiesList);
        }

        private static void DefaultConstraintSetup(IMethodOptions<object> options)
        {
        }
    }

You can see that the implementation of the first ActivityOf method is very quick and dirty. The idea was to keep this POC code simple enough to illustrate the technique.

The Failure Message

There is one minor issue with using test framework's Assert.IsTrue. When the assert fails, it simply tells you that you expected true but got false. I don't see this as a big issue because with today's tool you can easily click on the error to jump to the exact line of code. In most of time, the code gives much more information then the message itself. But hey, it is always good to have detailed message right?

Careful reader must already found the Assert method in the Mockery class, that is the one used to replace the Assert.IsTrue. It provides "better" message then true/false. The reason for the quotation marks around the word better is that at this moment, the implementation can sometime provide ambiguous message. Again this is just a POC, in order to provide accurate message, we will need to make deeper changes into the Rhino Mocks.

Below are some examples from the unit test:

            Mockery.Assert(mb.ActivityOf(b => b.Bar()).First
                .OccuredBefore(ma.ActivityOf(a => a.Act()).First)
                .OccuredBefore(mb.ActivityOf(b => b.Bar()).Last));

            (mb.ActivityOf(b => b.Bar()).First
                < ma.ActivityOf(a => a.Act()).First
                < mb.ActivityOf(b => b.Bar()).Last).AssertOccured();
            Mockery.Assert(foo.ActivityOf(f => f.Foo(1)) | 
                foo.ActivityOf(f => f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything)));

            (foo.ActivityOf(f => f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything)) | 
                foo.ActivityOf(f => f.Foo(1))).AssertOccured();

C# 2.0 / VS2005 Support

It was said that the AAA for C# 2.0 is ugly and tedious. I tried to using this new API with C# 2.0 syntax. The result is not too bad at all. Let's take a look at what we have in the unit test cases:

            Mockery.Assert(Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).Last
                .OccuredAfter(Mockery.ActivityOf(mb , delegate(IB b) { b.Bar(); }).Last)
                .OccuredAfter(Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).First));

            (Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).Last
                > Mockery.ActivityOf(mb, delegate(IB b) { b.Bar(); }).Last
                > Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).First).AssertOccured();

            Mockery.Assert(
Mockery.ActivityOf(foo, delegate(IFoo f) { f.Foo(1); }) | Mockery.ActivityOf(foo, delegate(IFoo f) { f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything); }));

Give It A Try

If you want to give it a try, I have build a version of Rhino Mocks based on last trunk source with the latest Castle DynamicProxy2 at this moment. You can find them here:

http://code.google.com/p/kennethxublogsource/downloads/list

There are also other features in the build like:

  • Create multi mock for AAA: Mockery.GenerateMultiMock
  • Create partial mock for AAA: Mockery.GeneratePartialMock and Mocker.GeneratePartialMultiMock

Update (7/9/2009): For people don't want a modified version of Rhino.Mocks, I have made an extension library without the feature of ordered expectation: Some Utilities Extend Rhino.Mocks

Tuesday, August 26, 2008

Integrate Web Service Client Generation into Build Process

In my last post, I presented a source code generator. It creates Web Service Client source code that implements our own domain interface. In this post, I'm going to talk about how we can integrate the generator into VS.Net.

Important Update: please also read ClickOnce Deployment + SGen Problem and The Workaround if the application is deployed through ClickOnce.

The interface between human and the generator

First of all I need a way to tell the generator what clients to generate. One idea from my colleague was to use the configuration file of Spring.Net. This is a great idea if we can get it to work but here are two major problems here.

  • It's not trivial to do this when property placeholder, abstract definition, sub-context and etc come into picture.
  • This strongly couples our generator to Spring.Net. I believe the generator is useful with or without Spring.Net.

So what I need is some sort of configuration file that let me specify the clients I want to generate. My generator must be able to easily read the configuration. Being both human and machine readable, XML is naturally the choice.

Below is an example of such an XML file (You can download all source code in this blog from http://www.codeplex.com/WSCodeGen).

<?xml version="1.0" encoding="utf-8" ?>
<web-service xmlns="urn:web-service-client-configuration-1.0">
  <client-group namespace="Example.Client.WebService" 
                xml-namespace="http://tempuri.org/">
    <client class-name="MyTestClient" xml-namespace="http://tempuri.org/">
      <interface>Example.Model.IComplex, Example.Model</interface>
      <interface>Example.Model.ISimple, Example.Model</interface>
    </client>
    <client class-name="HelloWorldClient">
      <interface>Example.Model.IHelloWorld, Example.Model</interface>
    </client>
  </client-group>
</web-service>

And yes, we have schema for it.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns="urn:web-service-client-configuration-1.0"
           targetNamespace="urn:web-service-client-configuration-1.0"
           elementFormDefault="qualified" attributeFormDefault="unqualified">
  <xs:element name="web-service">
    <xs:complexType>
      <xs:sequence maxOccurs="unbounded">
        <xs:element name="client-group">
          <xs:complexType>
            <xs:sequence maxOccurs="unbounded">
              <xs:element name="client">
                <xs:complexType>
                  <xs:sequence maxOccurs="unbounded">
                    <xs:element name="interface" type="xs:string"
                                maxOccurs="unbounded"/>
                  </xs:sequence>
                  <xs:attribute name="class-name" type="xs:Name" use="required"/>
                  <xs:attribute name="xml-namespace" type="xs:anyURI"/>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="namespace" type="xs:Name" use="required"/>
            <xs:attribute name="xml-namespace" type="xs:anyURI"
                          default="http://tempuri.org/"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Now our generator has grown from a single class to a console application project that takes an XML file and generates the Web Service client source code. With this enhancement, we can start to integrate with VS.Net.

VS.Net integration

I wish I could implement a VS.Net plug-in and/or template that it generates a Xyz.Designer.cs file based on an Xyz.xml (or may be a fancier one, Xyz.xwsc) file. Paulo Reichert's has a nice blog on this. I think I can implement what he described relatively quick. But the manual installation process of the plug-in kept me away from this solution. We have developers come and go and I don't want to be the one to answer questions like why this is not working for me. I guess I'll leave it aside until somebody is kind enough to tell me how to create an installer shell for it.

An easier, and actually also flexible approach is to make use of the VS.Net's build events. We can call the generator to generate the code in one of those build events to create the source file and then continue the build process. This worked pretty well for me and it also give a way to integrate with Spring.Net's XML configuration file. The generator command line takes an XSLT file that can be used to transform the XML configuration file to our Web Service client definition file. I'll cover the command line option in later section. Let's look at how it is setup in the example solution that you can download from http://www.codeplex.com/WSCodeGen. (Update 9/4: The example solution is updated with an additional project to workaround the ClickOnce Deployment problem)

The example solution consists of three projects illustrated in the following pictures. I think this is a simplified solution setup for most of the real world applications. If the application has only one project, I believe it is small enough to just write the clients manually.

 ExampleModel  ExampleClient  ExampleServer

We have the domain objects and interfaces defined in the Example.Model project. Both Example.Client and Example.WebService reference to Example.Model.

At the server side, you can find the HelloWorld Web Service that implements IHelloWord interface, and MyTestSerivce that implements both IComplex and ISimple interfaces.

At the client side, we created the WebServiceClient.xml file and a dummy WebServiceClient.cs file to start with. Creating the dummy file and have it included in the project is important so that the Example.Client will compile the generated clients. The XML is exactly was what I posted above.

Here comes the work horse. In the Build Events tab of the Example.Model project properties. We added command line below (in one line) to the post-build event.

$(SolutionDir)build\WebServiceClientGenerator\Debug\WebServiceClientGenerator.exe $(SolutionDir)example\Example.Client\WebService\WebServiceClient.xml

PostBuild

Rebuild the application and we should see that the WebServiceClient.cs file now contains the generated source code. Add code below to Program.cs and enable XmlSerializer diagnose in App.config.

static void Main(string[] args)
{
    IHelloWorld helloWorld = GetHelloWorldClient();
    Console.WriteLine(helloWorld.SayHello("WSCodeGen"));
    Console.ReadLine();
}
 
static IHelloWorld GetHelloWorldClient()
{
    HelloWorldClient helloWorld = new HelloWorldClient();
    helloWorld.Url = "http://localhost:3586/HelloWorld.asmx";
    return helloWorld;
}

Let's start the Example.WebService followed by the Example.Client. We can see that the temporary assembly along with other temporary files are created by XmlSerializer in your %TEMP% folder. This is because we haven't tell VS.Net to pre-compile the XmlSerializer.

The last step is to open the Build tab of the Example.Client project's properties. Change the "Configuration" to "All Configurations" and then change the "Generate serialization assembly" option to "On". Delete those generated files and run Example.Client again. In the mean time, monitor the %TEMP% folder, we should see no files are generated. Great!

ClientPropertySetting

Command line parameters

For the folks want to go beyond what was demonstrated in the Example solution, there are the command line parameters that you can use with the generator. Run the generator without any parameter will give you the usage help below.

Usage: WebServiceClientGenerator DefinitionFile [SourceFile] [XslFile]
    DefinitionFile:
        The XML definition file describes what Web Service clients to generate.
        If the XslFile is provide, the definition  file  is  transformed  using
        XSLT.
    SourceFile:
        Specifies the full path of the source file to be generated. If the file
        extension is not given, the default  source  file  extension  is  used.
        If this parameter is omitted,  default  to  the  definition  file  with
        extension replaced by the default source file extension.
    XslFile:
        If present, the definition file is transformed using this XSLT file.

I haven't gotten a chance to test it but theoretically, you should be able to pass a Spring.Net XML configuration file as the "DefinitionFile" and provide an XSL file to create the final definition file.

Future enhancement

I have started this as an open source project at http://www.codeplex.com/WSCodeGen. More API documentation is needed and Unit Test is still missing. If anybody is willing to help, thank you and please add to the comments.

Welcome suggestions and ideas, or just encouragements of creating a plug-in and template. I'll consider if there is enough demand.

I realized that we can do the same at the server side as well. It will make the next two enhancements possible if we have control at the server side.

I would like to be able to have the client throw the real exception instead of the meaningless SOAP exception that gets thrown universally by .Net framework. This is another architecture problem I would like to resolve.

I can also integrate the ability of using IList<T> and IDictionary<K,V> in web methods, we already have a solution for this using Spring.Net AOP with our home grown carrier classes. But I believe it would be much cleaner if we can do this here.

Monday, August 18, 2008

Utility methods for easier and better looking CodeDom program

I'm forced to get into the code generation business when I had to generate the Web Service client ourselves for a reason that I'll blog later. While CodeDom API is every powerful but all those CodeXXX classes quickly made my code generator program a monster.

This in turn forced me into creating a utility class and it did help to have my generator code organized. Now I can write below in one line:

CodeUtils.DefineClass(TypeAttributes.Public, "MyClass", baseType);

Here it is the utility class source code:

/// <summary>

/// Utility methods for generating source code using CodeDom.

/// </summary>

/// <author>Kenneth Xu</author>

public static class CodeUtils

{

    /// <summary>

    /// Single dimension array access expression.

    /// </summary>

    /// <param name="array">The array.</param>

    /// <param name="index">The index.</param>

    /// <returns>

    /// An instance of <see cref="CodeArrayIndexerExpression"/>

    /// </returns>

    public static CodeArrayIndexerExpression ArrayIndex(

        CodeExpression array, int index)

    {

        return new CodeArrayIndexerExpression(

            array, new CodePrimitiveExpression(index));

    }

 

    /// <summary>

    /// Determine the if a parameter is <see langword="ref"/> or

    /// <see langword="out"/>.

    /// </summary>

    /// <param name="parameter">The parameter from reflection.</param>

    /// <returns>Parameter direction</returns>

    public static FieldDirection DetermineParameterDirection(

        ParameterInfo parameter)

    {

        FieldDirection direction;

        if (parameter.IsOut)

            direction = FieldDirection.Out;

        else if (parameter.ParameterType.IsByRef)

            direction = FieldDirection.Ref;

        else

            direction = FieldDirection.In;

        return direction;

    }

 

    /// <summary>

    /// Define a new method.

    /// </summary>

    /// <param name="attributes">Custom attributes.</param>

    /// <param name="modifier">Access modifier.</param>

    /// <param name="returnType">Data type to return</param>

    /// <param name="name">Name of the method.</param>

    /// <param name="parameters">

    /// Parameter definition of this method.

    /// </param>

    /// <returns>A <see cref="CodeMemberMethod"/>.</returns>

    public static CodeMemberMethod DefineMethod(

        CodeAttributeDeclaration[] attributes,

        MemberAttributes modifier,

        CodeTypeReference returnType,

        string name,

        params CodeParameterDeclarationExpression[] parameters

        )

    {

        CodeMemberMethod method = new CodeMemberMethod();

        method.Name = name;

        method.Attributes = modifier;

        method.ReturnType = returnType;

        method.CustomAttributes.AddRange(attributes);

        method.Parameters.AddRange(parameters);

        return method;

    }

 

    /// <summary>

    /// Define a local variable in a <paramref name="method"/>.

    /// </summary>

    /// <param name="method">

    /// The method that new varaible will be defined in.

    /// </param>

    /// <param name="type">The type of the variable</param>

    /// <param name="name">The name of the variable</param>

    /// <param name="initializer">The variable initializer.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to the defined variable.

    /// </returns>

    public static CodeVariableReferenceExpression DefineVariable(

        CodeMemberMethod method,

        CodeTypeReference type,

        string name,

        CodeExpression initializer)

    {

        CodeVariableDeclarationStatement statement =

            new CodeVariableDeclarationStatement(type, name, initializer);

        method.Statements.Add(statement);

        return new CodeVariableReferenceExpression(name);

    }

 

    /// <summary>

    /// Define a parameter for the given <paramref name="method"/>.

    /// </summary>

    /// <param name="method">The method to define the paramter.</param>

    /// <param name="type">The type of the parameter.</param>

    /// <param name="name">The name of the parameter.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to defined parameter.

    /// </returns>

    public static CodeVariableReferenceExpression DefineParameter(

        CodeMemberMethod method,

        CodeTypeReference type,

        string name)

    {

        return DefineParameter(method, type, FieldDirection.In, name);

    }

 

    /// <summary>

    /// Define a parameter for the given <paramref name="method"/>.

    /// Optionally the parameter can be <see langword="out"/> or

    /// <see langword="ref"/>.

    /// </summary>

    /// <param name="method">The method to define the paramter.</param>

    /// <param name="type">The type of the parameter.</param>

    /// <param name="direction">To specify ref or out parameter.</param>

    /// <param name="name">The name of the parameter.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to defined parameter.

    /// </returns>

    public static CodeVariableReferenceExpression DefineParameter(

        CodeMemberMethod method,

        CodeTypeReference type,

        FieldDirection direction,

        string name)

    {

        CodeParameterDeclarationExpression parameter =

            new CodeParameterDeclarationExpression(type, name);

        parameter.Direction = direction;

        method.Parameters.Add(parameter);

        return new CodeVariableReferenceExpression(name);

    }

 

    /// <summary>

    /// Define a new class without custom attribute. It has no base

    /// class and doesn't implement any interface.

    /// </summary>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="typeParameters">

    /// Optional type parameter if this is a generic class.

    /// </param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        TypeAttributes modifier,

        string name,

        params CodeTypeParameter[] typeParameters)

    {

        return DefineClass(

            null, modifier, name, typeParameters, null, null);

    }

 

    /// <summary>

    /// Define a new non-generic class with no custom attributes.

    /// </summary>

    /// <remarks>

    /// Generic type parameters and custom attributes can be added later.

    /// </remarks>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="baseType">The base class.</param>

    /// <param name="interfaces">Optional interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        TypeAttributes modifier,

        string name,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineClass(

            null, modifier, name, null, baseType, interfaces);

    }

 

    /// <summary>

    /// Define a new non-generic class.

    /// </summary>

    /// <remarks>

    /// Generic type parameters can be added later.

    /// </remarks>

    /// <param name="attributes">Custome attributes for the class.</param>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="baseType">The base class, or null.</param>

    /// <param name="interfaces">Optional interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        string name,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineClass(

            attributes, modifier, name, null, baseType, interfaces);

    }

 

    /// <summary>

    /// Define a new class.

    /// </summary>

    /// <param name="attributes">Custome attributes for the class.</param>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="typeParameters">

    /// Type parameter if this is a generic class, null otherwise.

    /// </param>

    /// <param name="baseType">The base class.</param>

    /// <param name="interfaces">The interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        string name,

        CodeTypeParameter[] typeParameters,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineType(attributes, modifier, TypeType.Class,

            name, typeParameters, baseType, interfaces);

    }

 

    private static CodeTypeDeclaration DefineType(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        TypeType typeType,

        string name,

        CodeTypeParameter[] typeParameters,

        CodeTypeReference baseType,

        CodeTypeReference[] interfaces)

    {

        CodeTypeDeclaration ctd = new CodeTypeDeclaration();

        ctd.Name = name;

        ctd.TypeAttributes = modifier;

        switch (typeType)

        {

            case TypeType.Class:

                ctd.IsClass = true;

                break;

            case TypeType.Interface:

                ctd.IsInterface = true;

                break;

            case TypeType.Struct:

                ctd.IsStruct = true;

                break;

            case TypeType.Enum:

                ctd.IsEnum = true;

                break;

        }

        if (attributes != null)

        {

            ctd.CustomAttributes.AddRange(attributes);

        }

        if (typeParameters != null)

        {

            ctd.TypeParameters.AddRange(typeParameters);

        }

        if (baseType != null)

        {

            ctd.BaseTypes.Add(baseType);

        }

        if (interfaces != null)

        {

            ctd.BaseTypes.AddRange(interfaces);

        }

        return ctd;

    }

 

    private enum TypeType

    {

        Class,

        Interface,

        Struct,

        Enum

    }

}

After working with CodeDom for a while, I must say that the API was poorly designed. Although those utility methods helped a bit, but any program using this API still looks cumbersome and hard to maintain.

I'm hoping that I can have a fluent interface so that I can write like this:

DeclearType.Class.Attribute(...).Public.Virtual.Name("MyClass").Extends(baseType).Implements(...);

Utility methods for easier and better looking CodeDom program