Testing is a great use for dynamic languages, especially if the application being tested is written in a compiled language like C# or VB. Ruby is an especially interesting language for writing tests in due to the popularity of RSpec, a behavior-driven-development testing framework. IronRuby makes it really easy to test .NET applications because of it's .NET integration and ability to run real Ruby programs, like RSpec. However, this becomes much more difficult to do in Silverlight for a variety of reasons. And Silverlight doesn't have good options for testing to begin with, except for the Silverlight Unit Test Framework, but I yearn for something simpler. This post will show you how to test a C# Silverlight application with IronRuby, using a testing framework called Bacon, a lightweight RSpec clone.
Note: A similar exercise could be performed with IronPython and a Python testing framework like unittest or PyUnit, but I have much less experience with writing test code in Python. If anyone else does, please feel free to put together a similar package for IronPython).
Demo: testing a calculator on steroids
This is a example of testing a calculator (I'll explain the right-side with the Python code in the next post); the tests run in their own text-based REPL console, and even let you poke and prod the application after it's finished running the tests. The tests are very succinct and read easily, showing a big advantage to using IronRuby.
The tests do not actually click the buttons, as Silverlight doesn't have extensive UI automation support like WPF does, but the tests call the methods that are hooked to the events, and also test that the controls hook the correct method, making it an equivalent test, and a lot less magic-y.
Test any Silverlight app
Testing any Silverlight application is pretty straight-forward, so let's walk through how to set it up. You can start from either your own Silverlight application, or a new Silverlight application, however you'll need a website/web-application project to be serving the actual Silverlight application; if you're creating a new application simply use the default options.
First, download eggs.xap (I'll explain what it is later) and place it at the same location in your webserver as your application's XAP file is being served; most likely the ClientBin directory. If you put it anywhere else, make sure to account for that in the code below. Note: Eggs.xap will be part of agdlr in 0.5.1, which will come out in a week from this post's date.
Next, download this Web Application project, rename it to whatever you want, and add it to the same solution as your Silverlight application; this will hold our tests.
There is a Sample.Tests/tests directory in that project, with a single sample_test.rb file; that where the actual tests will go. You’ll also see eggs_config.rb; this file defines the list of tests we want to run, since Silverlight can't enumerate the files in a XAP file easily.
The clientaccesspolicy.xml file makes it possible to download the generated XAP file, since our Silverlight application is being served from a different web-server. You can move these tests to the same domain as the application, but for the sake of this demonstration they are different.
To get your application running the tests, add the following C# code to your application at the point which you want tests to run; this might be on Application_Startup, or at some later time ... it's up to you. If you're not sure, just add this code to the method which handles Application_Startup in App.xaml.cs to download eggs.xap and run the tests, only when the query string contains "test".
When it is all said and done, run your application with "?test" at the end of the URL, and you'll see something like this:
The tests run inside a HTML-based IronRuby REPL control, so you can inspect your application after the tests run, maybe to track down a failure or prototype your next test.
Note: if you don't see the REPL in your application, or just see the REPL and not the red menu bar below, the Silverlight control is blocking its view. If you make your Silverlight control "windowless", by adding
<param name='windowless' value='true' />" to the Silverlight object tag, or
Windowless='True' to the ASP.NET control which creates the object tag, it will allow the REPL to show up. If for some reason you can't make your control windowless, you'll need to set the width and height of the Silverlight control so it doesn't overlap the console.
How it works
Everything above is pretty transparent on what it's doing, except for two parts: (1) what is this "eggs.xap" thing, and (2) how did my tests magically become a XAP file?
"eggs.xap" is a small library to run Bacon in Silverlight. It containing bacon.rb, the Bacon framework, eggs.rb, a Bacon test runner for Silverlight, Eggs.dll, a simple .NET API for running the tests, and DLLs required to run IronRuby.
When you added the code to App.xaml.cs, it:
- Downloaded eggs.xap
- Pulled out Eggs.dll and loaded it
- Runs the tests in the supplied XAP via "Eggs.Start"
Eggs.Start loads IronRuby's DLLs and and uses the DLR Hosting APIs to call the Ruby
Eggs.run method. This is abstracted by Eggs.dll so the application being tested doesn't need a compile-time dependency on IronRuby.
All you did was put tests in a web application project, so how are they getting packaged up into a XAP file? In that project's web.config, there's one very important line:
Yes, Chiron's XAPing functionality is being used, but it's packaged in an ASP.NET HttpHandler, giving any ASP.NET application the ability to auto-XAP a directory when requested, without having to use Chiron's web-server functionality. So when you give
Eggs.start the Uri to the tests (http://localhost:35863/Calculator.Tests.xap), it resolves to that project, sees that the Calculator.Tests folder exists but no XAP file exists, so it creates one in memory and responds to the request with it. Here is the implementation if you are curious.
This same strategy can be used to develop Python or Ruby Silverlight applications in Visual Studio.
Now, get to testing!
Now you have no excuses for not testing your Silverlight application! Hit me up on twitter (@jschementi) or the comments if you have any questions.