Monthly Archives: February 2011

Continuous Integration using Phantom and MSDeploy

Phantom is a pretty sweet .NET build system by Jeremy Skinner written in C# and Boo.

I recently forked it on github as I wanted to add support for aspnet_compiler.exe and msdeploy.exe for use in one of our bigger projects.

These are just two simple wrappers for msdeploy.exe and aspnet_compiler.exe - basic and crude, but they do work.

With these in my build.boo script I can make a deploy simply by calling “Phantom.exe build publish deploy”

Nothing left but to set it up in Team City with the appropriate triggers ;)

The next step I guess is to throw in the updating of configuration files.

solution_file = "./solution.sln"
configuration = "release"

output_path = "./.output/solution.build"
publish_path = output_path+"/published.solution"

deploy_src = "iisApp=\""+publish_path+"\""
deploy_dest_test = "iisApp=\"website/application\",wmsvc=\"127.0.0.1\",authType=\"basic\",userName=\"admin\",password=\"god\" -allowUntrusted"
deploy_dest_stage = "iisApp=\"website/application\",wmsvc=\"127.0.0.2\",authType=\"basic\",userName=\"admin\",password=\"god!secure\" -allowUntrusted"

target default, (build, clean):
  pass

target build:
  msbuild(file: solution_file, configuration: configuration, properties: {"OutputPath": output_path, "TrackFileAccess": false})

target publish:
  aspnetcompiler(physicalpath: "./", virtualpath: "App", targetpath: publish_path)

target deploy, (deploy_test):
  pass

target deploy_test:
  msdeploy(skipfiles: (".*\\.config",".*\\.txt"),verb: "sync", source: deploy_src, dest: deploy_dest_test)

target deploy_stage:
  msdeploy(skipfiles: (".*\\.config",".*\\.txt"),verb: "sync", source: deploy_src, dest: deploy_dest_stage)  

target clean:
  rm(output_path)

Web Services: Automated feature testing using Specflow and Babelfish

example project at github

I recently started implementing BDD using Specflow for one of our customers.

Basically Specflow is a BDD framework written in .NET inspired by Cucumber and the tests are written in the same language (Gherkin)

One of the things we would like to include in this are the testing of soap service endpoints.

If you are a .NET developer you probably know all about the generation of proxy classes by using the wizard in visual studio or invoking wsdl.exe on the wsdl source file. This could be a problem and result in needless work, so I looked into compiling the proxy classes in memory during runtime.

Authentication

There exist a number of free and open soap web services out there, some that are used in my examples are taken from www.webservicex.net.

But of course we do have services that require some form of authentication. The following snippet illustrates how to perform forms authentication using the System.Net.WebClient and Babelfish.

public string FormsAuth(Uri url, string username, string password)
{
    var response = DownloadData(url);

    INode document = new Babelfish.HTML.HTMLDocument(Encoding.ASCII.GetString(response));

    var postData = string.Empty;

    var formElements = document.Find(n => n.Name.ToLower() == "input" && new[] { "hidden", "button", "submit", "checkbox", "radio", "text", "password" }
    .Contains(n.Attributes["type"]));

    foreach (var formElement in formElements)
    {
        postData += postData.Length > 0 ? "&" : string.Empty;

        var name = formElement.Attributes["name"];
        var value = formElement.Attributes["value"];

        switch (formElement.Attributes["type"])
        {
            case "text":
                postData += string.Format("{0}={1}", name, HttpUtility.UrlEncode(username));
                break;
            case "password":
                postData += string.Format("{0}={1}", name, HttpUtility.UrlEncode(password));
                break;
            default:
                if (!string.IsNullOrEmpty(value))
                    postData += string.Format("{0}={1}", name, HttpUtility.UrlEncode(value));
                break;
        }
    }

    Headers.Add("Content-Type", "application/x-www-form-urlencoded");
    string result;
    try
    {
        result = Encoding.ASCII.GetString(UploadData(url, "POST", Encoding.ASCII.GetBytes(postData)));
    }
    catch (WebException ex)
    {
        result = ex.ToString();
    }
    return result;
}

Web Service instantiation

Now when we have access to the wsdl definition via our modified WebClient we can use that to compile a SoapHttpClientProtocol object.

The following snippet is from the class ArbitraryWebservice and shows how to compile a wsdl stream into such an object given a valid type.

private static SoapHttpClientProtocol GenerateService(Stream stream, string serviceName)
{
    var serviceDescription = ServiceDescription.Read(stream);
    var serviceDescriptionImporter = new ServiceDescriptionImporter
    {
        ProtocolName = "Soap12",
        Style = ServiceDescriptionImportStyle.Client,
        CodeGenerationOptions = CodeGenerationOptions.GenerateProperties
    };

    serviceDescriptionImporter.AddServiceDescription(serviceDescription, null, null);

    var codeNamespace = new CodeNamespace();
    var codeCompileUnit = new CodeCompileUnit();

    codeCompileUnit.Namespaces.Add(codeNamespace);

    var warnings = serviceDescriptionImporter.Import(codeNamespace, codeCompileUnit);

    if (warnings > 0)
        throw new Exception(string.Format("{0}", warnings));

    using (var codeDomProvider = CodeDomProvider.CreateProvider("C#"))
    {
        var compilerParameters = new CompilerParameters(new[] { "System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll" }) { GenerateInMemory = true };

        var results = codeDomProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);

        if (results.Errors.Count > 0)
            throw new Exception(string.Join(", ", results.Errors.Cast<CompilerError>().Select(e => e.ToString()).ToArray()));

        var service = results.CompiledAssembly.CreateInstance(serviceName);

        return service as SoapHttpClientProtocol;
    }
}

Method invocation

The following snippet is also from the class ArbitraryWebservice.

Basically it attempts to map the given methodName to a method in the instantated service object. Then it iterates trough the parameters for that method and match each one to one of the elements in the args array.

The last part is to cast/convert complex parameter types to the correct type using a extension method (basically JSON serialization/deserialization).

public object Call(string methodName, MethodParamDescriptor[] args)
{
    var method = _service.GetType().GetMethod(methodName);

    if (method == null)
        throw new MissingMethodException(string.Format("this service contains no method named {0}", methodName));

    var methodParams = new List<object>();

    foreach (var p in method.GetParameters())
    {
        if(!args.Any(a => a.Name == p.Name))
            throw new ArgumentException("expected parameter not supplied", p.Name);

        methodParams.Add(args.Where(a => a.Name == p.Name)
            .Select(a => a.Value.Convert(p.ParameterType))
            .FirstOrDefault());
    }

    return method.Invoke(_service, methodParams.ToArray());
}

Specflow

Finally with the above in place we can write feature descriptions using Specflow and execute them as a normal unit test.

The complex object comparisons are done with the help of a Babelfish extension method

You can compile and run these tests yourself by downloading the solution from here.

Feature: Webservices
	In order to know what is going on
	As a developer
	I want to instantiate any arbitrary web service and invoke availible methods and assert their return values

@mytag
Scenario: Weather
	Given webservice named Weather with the wsdl url http://ws.cdyne.com/WeatherWS/Weather.asmx?wsdl
	When I call the method GetCityForecastByZIP with the parameter ZIP=36003
	Then I expect it to return an object matching content {City: 'Autaugaville', WeatherStationCity: 'Selma'}

@mytag
Scenario: Speed
	Given webservice named ConvertSpeeds with the wsdl url http://www.webservicex.net/ConvertSpeed.asmx?WSDL
	When I call the method ConvertSpeed with the parameters speed=100 and FromUnit=centimetersPersecond and ToUnit=feetPersecond
	Then I expect it to return the number 3.2808398950131235

@mytag
Scenario: Whois
	Given webservice named whois with the wsdl url http://www.webservicex.net/whois.asmx?WSDL
	When I call the method GetWhoIS with the parameters HostName=google.com
	Then I expect it to return a string containing TERMS OF USE: You are not authorized to access or query our Whois

@mytag
Scenario: country
	Given webservice named country with the wsdl url http://www.webservicex.net/country.asmx?WSDL
	When I call the method GetCountries with no parameters
	Then I expect it to return html containing <NewDataSet><Table><Name>Kiribati</Name></Table><Table><Name>Cocos (Keeling) Islands</Name></Table></NewDataSet>

@mytag
Scenario: GeoIPService
	Given webservice named GeoIPService with the wsdl url http://www.webservicex.net/geoipservice.asmx?WSDL
	When I call the method GetGeoIP with the parameters IPAddress='209.85.148.147'
	Then I expect it to return an object matching content {IP:'209.85.148.147', CountryName:'United States', CountryCode:'USA'}

@mytag
Scenario: BarCode
	Given webservice named BarCode with the wsdl url http://www.webservicex.net/genericbarcode.asmx?WSDL
	When I call the method GenerateBarCode with the parameters BarCodeText=123456 and BarCodeParam={Height:1,Width:1,Ratio:5,FontName:'Arial',FontSize:10,BarCodeImageFormat: 'PNG'}
	Then I expect it to return a base64 string matching iVBORw0KGgoAAAANSUhEU...AAAAAAAAAAAAAAAAAAAAAAAAAAAAA==