Incorporating Growl For Windows into my Application

I’m a big believer in re-use. Code I don’t aihave to write is code I don’t have todebug or maintain. When I discovered that I’d really like some notificationtoast in my Windows Forms enterprise application, I immediately started lookingfor libraries.

The definitive toast solution for Mac OS is Growl.Growl is a separate application/control panel that runs on your system and managesnotifications from all subscribing services on your system. No more overlapping toast.It also provides a lot of filtering, control, and plugin-based extensibility. Didyou want your ‘new mail’ notifications to show up on your cell phone as SMS ratherthan on your desktop? Just do it. Want the toilet to flush when the build fails? Getan Arduino and script it up!

Needless to say I was really chuffed to find the Growlfor Windows project. Their application is still in beta, but the developers arevery responsive, and they are checking code fixes in within hours of getting bug reportswhen they can. GfW can provide you notifications for your GMail, Outlook, Visual StudioBuilds, and the current song playing in Pandora.

Installing Growl With My Application

My application installs with NSIS. Addingautomatic installation of the Growl client to my installer was very quick. I grabbedthe .msi file from the Growl download, and put it within my build tree. Then I addedthe following snippet to my NSIS script:

Section "GrowlInstall" SEC01File "GrowlWindows Deployment.msi"ExecWait 'MSIEXEC.EXE /I "$INSTDIRWindows Deployment.msi"/QB- ALLUSERS=1'SectionEnd

That’s it for the install. However, I wanted it to uninstall cleanly as well, so Iadded the following line to the uninstall section:

ExecWait 'MSIEXEC.EXE/x "$INSTDIRWindows Deployment.msi" /qn'

Two caveats:

  1. You may not want to uninstall Growl automatically for the user, in case they had installedit on their own and want to uninstall your app, but keep Growl. This isn’t an issuein my business deployment, but it may be one for a public application.
  2. There may be some issues with UAC installs, and I haven’t tested this yet. I thinkI can just run my NSIS exe ‘As Administrator’ and have it work, but there may be issueswith msiexec and UAC.

Code to Send Notifications

First things first, lets establish some interfaces to program to, that way we canswap out implementations for testing and if our needs change.

public interface INotificationMessage{string MessageType { get; }string MessageDescription { get; }string MessageId { get; }string MessageText { get; set; }Image MessageIcon { get; }Action AcceptMessageCallback { get; set; }Action DeclineMessageCallback { get; set; }}public interface INotificationSender{void SendMessage(INotificationMessage message);}

Next, we’ll implement a specific Notification Message type. Each Notification Messagetype can be instantiated of a specific type so that it’s very easy to add new typesas well as very easy to use them. Each type can have an icon of it’s own.

public class ApplicationErrorNotificationMessage: INotificationMessage{public string MessageType{ get; private set; }public string MessageDescription{ get; private set; }public string MessageId{ get; private set; }public string MessageText{ get; set; }public Image MessageIcon { get; private set;}public Action AcceptMessageCallback { get; set;}public Action DeclineMessageCallback { get; set;}public ApplicationErrorNotificationMessage(){this.MessageType = "APPLICATION_ERROR";this.MessageDescription = "ApplicationError";this.MessageId = Guid.NewGuid().ToString();this.MessageIcon = Application.Properties.Resources.AppIcon;}}

It appears that growl caches the message icons, so you might need to restart the Growlprocess if you go changing your Notification icons.

Finally we have our implementation of INotificationSender which registers with Growl,ensures that it is installed, and ensures that it is running.

public class GrowlNotificationSender: INotificationSender{private GrowlConnector connector;private Image applicationIcon;private string applicationName;private INotificationMessage[] messageTypes;private Dictionary<string,INotificationMessage> sentMessages;public GrowlNotificationSender(INotificationMessage[]messageTypes,Image applicationIcon,string applicationName){this.connector = new GrowlConnector();this.messageTypes = messageTypes;this.applicationIcon = applicationIcon;this.applicationName = applicationName;this.sentMessages = new Dictionary<string,INotificationMessage>();EnsureGrowlIsRunning();RegisterWithGrowl();}private void RegisterWithGrowl(){Application thisApplication = new Application(applicationName);thisApplication.Icon = applicationIcon;List<NotificationType> notificationTypes = new List<NotificationType>();foreach (var messageType in messageTypes){NotificationType notificationType =new NotificationType(messageType.MessageType,messageType.MessageDescription);notificationType.Icon = messageType.MessageIcon;notificationTypes.Add(notificationType);}this.connector.Register(thisApplication, notificationTypes.ToArray());this.connector.NotificationCallback += new GrowlConnector.CallbackEventHandler(connector_NotificationCallback);this.connector.EncryptionAlgorithm = Cryptography.SymmetricAlgorithmType.PlainText;}public void EnsureGrowlIsRunning(){Process[] processlist = Process.GetProcesses();foreach (Process theprocess in processlist){if (theprocess.ProcessName.Equals("Growl")){ return; }}Detector detector = new Growl.CoreLibrary.Detector();if (detector.IsAvailable){System.Diagnostics.Process.Start(detector.InstallationFolder + @"Growl.exe");Thread.Sleep(1000);return;}else{throw new FileNotFoundException("Growlnot installed?");}}void connector_NotificationCallback(Response response,CallbackData callbackData){if (sentMessages[callbackData.Data] != null){if (callbackData.Result == CallbackResult.CLICK){if (sentMessages[callbackData.Data].AcceptMessageCallback!= null){sentMessages[callbackData.Data].AcceptMessageCallback.Invoke();}}else{if (sentMessages[callbackData.Data].DeclineMessageCallback!= null){sentMessages[callbackData.Data].DeclineMessageCallback.Invoke();}}sentMessages.Remove(callbackData.Data);}}public void SendMessage(INotificationMessagemessage){this.sentMessages.Add(message.MessageId, message);CallbackContext callbackContext = new CallbackContext();callbackContext.Data = message.MessageId;callbackContext.Type = message.MessageType;Notification notification = new Notification(this.applicationName,message.MessageType,message.MessageId,message.MessageDescription,message.MessageText);EnsureGrowlIsRunning();this.connector.Notify(notification, callbackContext);}}

This implementation keeps track of sent messages, and executes callbacks on them dependingon the user’s response. If you don’t want to use one of the callbacks, just leaveit undefined.

I haven’t found a way to get Growl to send the CLOSE message rather than the TIMEOUT,but that’s ok, since I treat them the same.

Using The Code

You’d probably want to wire this up with your IOC container, but here’s a manual usage.

INotificationSender sender = new GrowlNotificationSender(new[] { new ApplicationErrorNotificationMessage()},Moneta.Properties.Resources.Crystal_128_package_network,"My Application");Thread.Sleep(1000);ApplicationErrorNotificationMessage message = new ApplicationErrorNotificationMessage{MessageText = "Hi There",AcceptMessageCallback = () => MessageBox.Show("Youclicked"),DeclineMessageCallback = () => MessageBox.Show("PayAttention!")};sender.SendMessage(message);

It’s worth noting that the first registration of your app may take a moment for Growlto process, and that’s why I have the Sleep() in there. If your app doesn’t intendto immediately send a message after it’s first registration, that’s unnecessary.

Resources

Windows Forms Eventing: Thread Synchronization

So last time, we created this great event aggregator for our Windows Forms applications.Instead of having the code that sends messages directly connected to the code thatreceives messages, everybody just knows about the event aggregator. This works thesame way you don’t need turn-by-turn directions to Fox News Headquarters to mail thema box of dirty diapers.

The catch is that the code so far only works if all the senders and receivers areon the same thread. If they are on different threads, who knows what may happen? (Notme. I failed out of Home-Ec. I don’t even know how you turn cotton flowers into threads,let alone how to make them fit together.)

There is a simple fix, however. The .NET 2.0 SynchronizationContext is used by WindowsForms and can provide an easy-to-use central choke point to manage all of our thread-to-threadcommunications.

Remember Your Singleton

When I was cutting and pasting together this code from the Intarnets, one big problemI had was forgetting to initialize my singleton correctly. It’s really importantthat you initialize your singleton EventAggregator from SynchronizationContext.Currentin the Windows Forms Thread. Setting the thing up right in StructureMap oryour IOC Container of choice works just great. Just make sure it gets done. (My mistakealso involved letting StructureMap use “new SynchronizationContext()” in initializingmy singleton rather than “SynchronizationContext.Current”.) Get it right the firsttime and you won’t have to do multithread debugging.

EventAggregator Class

public class EventAggregator: IEventAggregator{private readonly SynchronizationContext_context;private readonly List<object>_listeners = new List<object>();private readonly object _locker= new object();public EventAggregator(SynchronizationContextcontext){_context = context;}#region IEventAggregator Memberspublic void SendMessage<T>(Tmessage){sendAction(() => all().CallOnEach<IListener<T>>(x => { x.Handle(message);}));}public void AddListener(object listener){withinLock(() =>{if (_listeners.Contains(listener)) return;_listeners.Add(listener);});}public void RemoveListener(object listener){withinLock(() => _listeners.Remove(listener));}#endregionprivate object[]all(){lock (_locker){return _listeners.ToArray();}}private void withinLock(Actionaction){lock (_locker){action();}}protected virtual void sendAction(Actionaction){_context.Send(state => { action(); }, null);}}

Notice the locking. Notice the creation of a copy of the _listeners list into an arrayfor Thread Safety. Most importantly, notice the use of _context.Send(). A few minorchanges…but now the whole class is thread safe, and synchronous between threads. Hooray!

Next Time

I’ve still got lots to cover on this subject. How do you use the eventing in yourapplication architecture. Using Weak References to protect your event system frommemory leaks. Sending events to a specific target or set of targets. I’m going tobe working on some other projects for a bit, so it may be a while before I write thoseposts, but hopefully what we’ve covered so far is useful on its own until then.

Resources

Design Principles

Digging out the Draft Bucket: This isn’t really a completed post, but rather thanleave it in my Draft folder forever, I’m just going to post it. That way it’s infamywill live forever.

There’s a massive summary of design principles in the ASP.NETMVC Forums project introduction. The actual code in the project is opaque to me.I’d need to learn the framework before I’d understand how he’s working on it.

Rocky on TDD, with anexplanation on how comprehensive testing means writing more test code than real code.”No one actually does this.” “I once watched a TDD (and MVC/MVP) presentation.The speaker wrote several pages of code to build and test a presenter that did a bunchof work. Nice stuff, until you realized that all that could have been done in 1-3lines of code using data binding. I asked him why he did this rather than using databinding. The answer: you can’t test data binding. I’m afraid my jaw dropped. See,I have a wife and kids. I like to get home and spend time with them. If I can write3 lines of code, or write 3 pages of code that I need to test and debug, I’m goingto pick the 3 lines of code every time”

Authorization system inCSLA.

Polymorphic MV*.

For when I need some new article materials: ABig Hanselman Article on Code Learning Online.

Cohesionand Coupling

Articles on SOLID:

  1. SingleResponsibility Principle
  2. Open/ClosedPrinciple
  3. LiskovSubstitution Principle

Windows Forms Eventing: Generic Pub/Sub

In my lastpost, I covered how you can implement a publish/subscribe system for sending eventsbetween components of your Windows Forms application. Using this model, it is easyto create separate components that don’t rely on each other’s implementation detailsin order to provide a consistent experience for the user. A button on the toolbarcan be disabled by an action on the data entry screen…without hard references betweenthe two.

However, the implementation from last time required a new aggregator and set of interfacesfor each message type that needed to be passed around. Let’s fix that with generics.

Listener Interface

First we replace the IEventReciever interface from last time with a generic IListenerinterface that uses a generic type for the message object.

public interface IListener<T>{void Handle(T message);}
All your subscribers now implement an IListener<Message> for each messagetype they can handle. (This is great because one class can listen for multiple typesof messages!)

Event Aggregator Interface

Similarly, the Event Aggregator needs a generics reset. Notice that the Add and Removemethods now accept any old object.

public interface IEventAggregator{void SendMessage<T>(T message);void AddListener(object listener);void RemoveListener(object listener);}

Event Aggregator

public class EventAggregator: IEventAggregator{private readonly List<object>listeners = new List<object>();#region IEventAggregator Memberspublic void SendMessage<T>(Tmessage){listeners.CallOnEach<IListener<T>>(x => { x.Handle(message); });}public void AddListener(object listener){if (listeners.Contains(listener)) return;listeners.Add(listener);}public void RemoveListener(object listener){listeners.Remove(listener);}#endregion}

Not much new here….except for that CallOnEach method. Where did that come from?

Extension Methods

We need to add a few utility methods to the IEnumerables so that we can send our messages:

public static void CallOnEach<T>(this IEnumerableenumerable, Action<T> action) where T : class{foreach (object o in enumerable){o.CallOn(action);}}public static void CallOn<T>(this object target,Action<T> action) where T : class{var subject = target as T;if (subject != null){action(subject);}}

Put those in a likely static class somewhere.

Are we there yet?

This Event Aggregator is getting powerful. We should kill it before it develops languageskills.

With the code we have so far, we can easily create a new message in the system withoutmodifying the aggregator code. I like adding the methods as child classes of theirreceivers (when they are receiver-specific).

There is, however, still a problem. In the type of application that needs this eventingsystem, you are likely going to need to do some background threading to keep the UIresponsive. The Event Aggregator we have doesn’t do anything to keep itself or therest of the application synchronized.

What happens when a background thread sends a message that the receiver needs to acton by talking to the UI thread? Do you write a bunch of Invoke() code everywhere?

As it turns out, there is a better way. And my next post will show you how to upgradeyour EventAggregator to be the thread master!

Windows Forms Eventing: Adding Pub/Sub

In my lastpost, I described the creeping problem I’ve been having with wiring my large WindowsForms application up with EventHandler delegates. In short, the design becomes brittle—it’shard to write and hard to change. Luckily, the solution is an easy one: Publish/Subscribe.

The Event Aggregator is a singleton. You can either manage this yourself, or you canhave your Inversion of Control container do it for you. When an Event Receiver iscreated, it registers itself with the Event Aggregator, which stores a reference tothe receiver. When a Event Sender sends a message to the Event Aggregator, the aggregatorloops through all the registered receivers and calls a method on them to handle theevent. This is facilitated by having the receivers implementing an interface. Here’sa simple implementation:

public class EventMessage{public string MessageText{ get; set; }}public interface IEventReceiver{void Handle(EventMessage message);}public interface IEventAggregator{void SendMessage(EventMessage message);void AddReceiver(IEventReceiver receiver);void RemoveReceiver(IEventReceiver receiver);}public class EventAggregator: IEventAggregator{private readonly List<IEventReceiver>receivers = new List<IEventReceiver>();#region IEventAggregator Memberspublic void SendMessage(EventMessagemessage){receivers.ForEach(r => r.Handle(message));}public void AddReceiver(IEventReceiverreceiver){if (receivers.Contains(receiver)) return;receivers.Add(receiver);}public void RemoveReceiver(IEventReceiverreceiver){receivers.Remove(receiver);}#endregion}public class EventReceiver: IEventReceiver{public EventReceiver(IEventAggregator aggregator){aggregator.AddReceiver(this);}#region IEventReceiver Memberspublic void Handle(EventMessagemessage){Console.WriteLine(message.MessageText);}#endregion}public class EventSender{private IEventAggregator aggregator;public EventSender(IEventAggregator aggregator){this.aggregator = aggregator;}public void SendErrorMessage(string message){aggregator.SendMessage(new EventMessage { MessageText= message });}}

This is a big improvement over manual event wiring through an application. No longerdo the targets of a message need to know (have references to) the senders of the messagein order to make the connection. You can add a new target (receiver) without changingthe code of the sender. You can add a new sender without changing the code of thereceivers. And you don’t have to manually map things out in some sort of registryclass. This is simple easy and it will work.

It’s always something with you, isn’t it?

Ok. I like it. I’m happy. But how many of these things am I going to have floatingaround? It seems like a lot of code to write to hand one type of event between somecomponents. I’m going to have to hire some Indian outsourcing company to write allof these singleton aggregators.

This sounds like a job for generics! My next post will rewrite the Event Aggregatorso that a single aggregator can handle every message your application has.

Windows Forms Eventing: The Problem

Anxiety. This is a feeling I’ve been learning to recognize as a SIGN when I’m coding.The feeling you get when the camera gets really close on the face of the pretty girlas she walks around the empty, and silent, house. Is she going to turn around suddenlyand be given flowers? Or is the disemboweling only moments away. You don’t know, butyou’re cringing either way.

I’ve felt this in the past about data access layers. This is the feeling I’ve beenaccumulating around my Windows Forms application lately. I don’t want to code. I’mafraid to code. It’s going to be painful. I’m not sure what’s going to go wrong. Butsomething will, and I’ll be sucked into the unproductive death pit.

Having spent a bit of time feeling around the problem, I’m pretty sure what I don’tlike is having to deal with events. Let’s take a look at the reason why:

Windows forms makes things easy.

Click click click! I have a button and I have an event linked to it. Amazingly productive.My app will be running in no time.

Event Handlers are teh awesome.

It’s so easy to link an action to an event handler. Well. It’s pretty easy. It’s veryeasy within one class. Not so bad if you’ve got a reference to the object that containsthe event handler. A little bit of encapsulation makes it harder. Chained event handlerscan fix that though……

All is happy with one form per task.

This form is easy. Add a few buttons and it’s great. If you’re prepared to make yourapplication out of a bunch of forms that the user will work with one-by-one, there’sno problem.

However….sovereign apps don’t look like that.

If the user is going to be in one application all day long, it will have to provideeasy access to a number of different types of functionality. The application can’tconsolidate all of the interface elements required for a single task into a singleplace. Rather there’s a spaghetti monster of functionality, reaching it’s noodleyappendages everywhere.

Events flying every which way, and everything needs a hard reference to everythingelse.

Can you actually wire all this up with EventHandler references? Sure. Of course youcan. However, you end with a lot of tightly coupled code that is a real mess to createand to maintain.

Hope. The eternal struggle.

A application shell is a complicated beast. But problems have solutions. In my nextpost, I’ll show how you can integrate all these separate components without stranglingyourself in wiring.

Adding ELMAH to an ASP.NET Site

ELMAH (Error Logging Modules and Handlers)is a fantastic library that provides error logging and troubleshooting support toan ASP.NET web site. You practically just drop it in and BOOM, you’ve got great exceptionreporting.

Step 1: Add A Reference

Add a reference to the ELMAH DLL in your ASP.NET project. (They tell me it just needsto be dropped in the BIN folder, but that almost seems like more work to me.)

Step 2: Add Config Sections

In web.config, add the following lines to <configSections>

<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler,Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler,Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler,Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler,Elmah"/>
</sectionGroup>
</configSections>

Step 3: Add the ELMAH Section

<elmah>
<security allowRemoteAccess="0" />
<errorLog type="Elmah.XmlFileErrorLog,Elmah" logPath="|DataDirectory|" />
</elmah>

Step 4: System.web—httpModules and httpHandlers

<system.web>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule,Elmah"/>
</httpModules>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory,Elmah" />
</httpHandlers>
</system.web>

Step 5: (II7 Only) Configure system.webServer

<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<add name="Elmah.ErrorLog" type="Elmah.ErrorLogModule,Elmah" preCondition="managedHandler" />
<add name="Elmah.ErrorFilter" type="Elmah.ErrorFilterModule" preCondition="managedHandler" />
<add name="Elmah.ErrorMail" type="Elmah.ErrorMailModule" preCondition="managedHandler" />
</modules>
<handlers>
<add name="Elmah" path="elmah.axd" verb="POST,GET,HEAD" type="Elmah.ErrorLogPageFactory,Elmah" preCondition="integratedMode" />
</handlers>
</system.webServer>

Step 6: Secure remote access using ASP.NET membership

<location path="elmah.axd">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>

Resources:

I don’t know what a number is anymore!

One, Two, Three, Four. I thought I understood the damn things. And then it turns outthey have ‘lifestyle choices’ and Mr. One says ‘you know, I just don’t FEEL like anInteger’ and suddenly they are making ‘changes’ and you’re alone with the Xbox, apound of bacon, and nothing but tears.

Floating Point Numbers

I’m talking numbers in Ruby right now, but I’m shaken to the core. Floating numbersare numbers! They should work right! They should ADD UP! Now, a floatingpoint number is at least equal to itself. This passes:

assert_equal(0.1, 0.1)

But this doesn’t:

assert_not_equal(0.1, 0.4-0.3)

You’d think 0.1 == 0.1 but you’d be wrong! You see, 0.1 is only an approximation ofthe value stored by the floating point system in binary. 0.3 is an approximation,and 0.4 is an approximation. And when they are mathed, they don’t approximate outequally. There’s a loss of precision. It’s pretty close. This passes:

expected_float = 0.1
actual_float = 0.4-0.3
assert ( (expected_float - actual_float).abs <= 0.00000005 )

…it’s just not exact. It’s wrong over in C# too.

[Test]
public void FloatTests()
{
Assert.AreEqual(0.1f, 0.3f - 0.2f);
}

Goes boom:

Modulus

What’s a modulus? First off, I thought it was the number you get when you use themod operator in a language…but it’s not. The modulus is actually the numberyou ‘divide’ by in a mod operation. The result is technically called the remainder.

Now the way I learned it, the mod operation is a lot like making change. If I have1453 copper pieces (cp) and one silver piece (sp) is 100 coppers, then I find outhow many silvers I have with simple division. This passes in C#:

int copper= 1453;
int silver= copper / 100;
int change= copper % 100;
Assert.AreEqual(14, silver);
Assert.AreEqual(53, change);

And in Ruby:

copper = 1453
silver = copper / 100
change = copper % 100
assert_equal 14, silver
assert_equal 53, change

It’s all good so far, right? But what if I owe money? What if my initial total is–1453 ? I owe 14 silvers and 53 coppers, right? Well, yes. And that’s what C# shows.–14 silver and –53 coppers. But not Ruby:

copper = -1453
silver = copper / 100
change = copper % 100
assert_equal -15, silver
assert_equal 47, change

You owe 15 silver. And when you pay…you’ll get 47 copper back. If you want the C#(C++, C, etc) results, you’ll need to use positive numbers and handle the negativeyourself.

I’ve been scratching my head around this, and it really seems like it’s an equallyvalid way of the numbers. The math people have both definitions lying around. Andsome cases have this method make more sense. Imagine you’re trying to find out whatO’Clock it is. You have your hours variable and you use the mod operator. HOURS %12. That works just fine, until you go negative. What if you wanted to know what O’Clockit was 143 hours ago? If it’s noon, and you’re using Ruby, –143 % 12 gives you theright answer.

I guess the accountants are right. “What do you want the numbers to be?”

Test-Driven Learning Games and Tests

In my first and second poston the idea of learning Ruby through writing tests, I showed the basics of learninga language using the TDD tools. Since that start, I’ve found some great work othershave done along these lines that you can use to play with the skills you’re learningand test yourself against some real problems.

First, the Ruby Koans byJim Weirich are a comprehensive set of ‘learning tests’ that can teach you or quizyou on what you’ve learned of Ruby so far. Each time you pass a test, it points youto the next thing you need to fix. Truly TDD learning!

I was a bit put off when I first found this set, since it seems so much like a shortcut,but there’s no reason I can’t play with Jim’s tests as well as writemy own, now is there?

Second, there’s the Ruby Quiz, whichlooks to be a great place to get a graduate education in your Ruby skills. The quizisn’t being added to anymore, but the challenges are just awesome and they have referencesolutions from people who have solved them before you. Once you’ve solved one, youcan find so many ways of learning a better way. Talk about erudition!

A Testy Way of Learning Ruby

In my recentpost, I started sharing some of the fun of learning Ruby using Test-Driven techniquesto learn the language features and document what you are learning as you go.

There are several advantages of learning the language this way. It flexes your brainin ways that just reading the book or typing in the sample code doesn’t. Once you’redone, you have some running, organized code that you can refer back to. Also, whenyou upgrade your language, or change which platform you run it on, you can run yoursuite of learning tests. Find out if Ruby 1.9 is different from 1.8…and how. Are thereimplementation differences between linux Ruby and Iron Ruby? You’ll know.

Today, I’d like to cover the additional assert variants available in the Test::Unitmodule, and talk about using an IDE.

Lasttime, we covered the assert method for testing truth. But there are lot more thingsyou can test!

  • assert – Tests the truth of an expression
  • assert_block – Tests that the return value of a code block is true
  • assert_equal – Tests that two values are equal
  • assert_in_delta – Tests two floats with tolerance for floating point error (read upon this one! it’s a doozy)
  • assert_instance_of – Tests that an object is an instance of a class
  • assert_kind_of – Tests that an object is of a class, or a subclass, or implementsthe kind
  • assert_match – Test using a regular expression
  • assert_nil – Tests the nil-ness of the expression
  • assert_no_match – A not test
  • assert_not_equal – A not test
  • assert_not_nil – A not test
  • assert_not_same – A not test
  • assert_nothing_raised – A not test
  • assert_nothing_thrown – Not a test. Just kidding.
  • assert_operator – I don’t get this one. Well, I’m still learning!
  • assert_raise – Tests that the code block raises a given exception
  • assert_respond_to – Tests that an object has a named method defined
  • assert_same – Tests that both values are the same object instance
  • assert_send – Another one to learn on. Looks like it tests a method call?
  • assert_throws – Looks like another type of exception handling.

With this group of methods, it should be rather easy for you to write some nice, readabletests as you learn things up. One interesting one is assert_same, which can provethat all values of True are really the same instance. Also, when you write a testthat fails by raising an exception, use assert_raise to document that. You shouldhave a test that shows what happens when you divide by zero, and one that shows whathappens when you use an uninitialized variable.

Using an IDE

Most of the ‘True Ruby Folk’ believe with all their flinty hearts that you shouldn’tneed anything more than a charred and pointed stick to write excellent Ruby. Theyare probably right. On the other hand, a good IDE can be like a coding video gameand make the process so much more colorful and fun. I’ve sure found that JetBrains’new RubyMine IDE has made myearly Ruby coding a bit of clicky goodness.

Creating a new test class is really easy. You just add the file to your project:

You get a fully fleshed and voluptuous test class ready for you to have your way withit.

And while you’re learning the language, it’s really nice to have some code completionfeatures so that you can figure out the rest of what you’re typing without going madswitching between windows.

All that and it’s not going to break the bank. Just $99! Give it a try.

I hope you’re keeping up with your test writing. I’ve got 168 assertions testing languagefeatures and I’m just through the primitives and variable types.

How many tests can you write?