Monthly Archives: February 2008

GUI models allow for compile time error detection of changes that break test automation

When I was working at CodeGear one of the tools I wrote generated models of .DFM’s (Delphi’s form file format) for use with GUI automation. A model is a class that mirrors the control hierarchy found on a form with classes that can perform automation tasks (clicking, typing, determining location etc.) against the controls. For more information on models refer to this post.

Modeling a Form, an Example

Below is an example of a simple Delphi form and it’s corresponding model class:

Delphi Form Class Corresponding Model
type
TMyForm = class(TForm)
MessageText: TLabel;
Yes: TButton;
No: TButton;
Cancel: TButton;
...

end;
type
TMyFormModel = class(TBaseDlg)
MessageText: TLabelGem;
Yes: TButtonGem;
No: TButtonGem;
Cancel: TButtonGem;
...

end;

The model is generated off of the .DFM which looks like this:

object Form1: TForm1
  Left = 217
  Top = 88
  Width = 1082
  Height = 749
  Caption = 'Form1'
  object MessageText: TLabel
    Left = 19
    Top = 13
    Width = 32
    Height = 13
    Caption = 'Label1'
  end
  object Yes: TButton
    Left = 15
    Top = 43
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
  end
  object No: TButton
    Left = 101
    Top = 43
    Width = 75
    Height = 25
    Caption = 'Button2'
    TabOrder = 1
  end
end

Delphi’s VCL component library supports Visual Form Inheritance and since there isn’t enough contextual information to reconstruct the form’s class hierarchy without compiling the code the model generator is provided a file that contains these details so the generated models exactly mirrors the application’s form inheritance hierarchy not just the form itself.

So Why is a Model Generator Important?

At first, you might think sure, the model generator is going to save you lots of time coding which is entirely true. But, that’s not the only nor the largest benefit of using generated models. An additional benefit is that the models are compiled using a statically typed language providing for compile time error detection. For example let’s say a developer renames the “Yes” button to “YesBtn” or deletes the MessageText label?

If the model had been hand written or statically generated, which is the situation CodeGear was in, the error would only surface once the test suite had executed. At that point, log file analysis would have to be performed to distinguish between a real bug and a automation error, not a good situation to have QA people in day after day. With generated models these kinds of errors can be detected at compile time allowing for R&D to assess the impact of the change on QA’s automation and not vise versa. Btw, this also underscores the fact that the model generator can provide alias functionality thus preventing simple name changes from impacting test automation.

The faster developers can find out they’ve broken existing test suites the more likely the problem can be corrected even in the event that not all test suites are executed every build. Yet another benefit of continuous integration.

There are several other benefits like:

  • Give R&D the ability to determine the impact a given change will have on the existing test automation
  • Provide insight into the depth of testing through static code analysis and evaluating which models as well as which parts of models are being exercised
  • Allow R&D/QA to quickly automate new UI

Conclusion

Backing your GUI testing with compile time error checking allows you to leverage GUI test automation in several ways. In addition, a development team will have much more visibility into the impact a given change will have on the existing test automation as well as provide insight into areas that need additional testing.

It’s a shame the test framework developed at Borland starting back in 1994 has never made it into the hands of developers outside the company. Perhaps I should look to start an Open Source project for model driven testing using C# based on  IAccessible. Btw, I just Googled on IAccessible and the post I linked to is in the top ten, meaning this advice really does work.

See also: Automation

Mozilla Prism and composing new GMail messages

Gmail Plain Text link

Awhile ago, I mentioned Mozilla’s Prism  which allows you to run websites in a manner which more closely resembles a desktop application within a standalone window. When using $g(GMail) the default Rich text editor for the message body doesn’t work and the editor appears read only. The key is to click the “Plain Text” link and switch the editor style which will allow you to type your message.

Since Prism was initially released I haven’t heard/read anything else about it’s development so hopefully we’ll see improvements/fixes. If you haven’t at least played with Prism I highly recommend.

An FTP Source Control Provider for CruiseControl.NET

I’ve been working on a custom CCNET implementation to handle EDI invoicing which is nearly complete and one of the things I needed was FTP download support. In the project I’m working there is a utility that makes use of edtFTPnet, from Enterprise Distributed Technologies, an Open Source C# FTP library licensed under LGPL. It seems to work reasonably well though the authors are admittedly “love the ‘internal'” keyword thus making extensibility difficult, if not impossible, short of forking the codebase. Anyway, I’ve been using the library and it seems to work just fine. Btw, I’ve submitted my suggested changes so hopefully they’ll recognize the benefits and incorporate them.

Writing Plugins for CruiseControl.NET

Anyway, what I needed as a mechanism that would poll an FTP server looking for new files to be downloaded which sounded like a reasonable thing for CruiseControl.NET. Of course, once files are downloaded additional processing is done so it fits with CI quite nicely. Fortunately, CruiseControl.NET has a nice plugin architecture that makes this sort of thing easy.

Creating a Plugin Assembly

The first step is to create a new Class Library following the naming convention ccnet.*.plugin.dll and add the following assembly references:

  • edtFtpnet
  • Thoughtworks.CruiseControl.Core.dll
  • NetReflector.dll

And add the following to your using statements:

  • System.Collections
  • EnterpriseDT.Net.Ftp
  • Exortech.NetReflector
  • ThoughtWorks.CruiseControl.Core
  • ThoughtWorks.CruiseControl.Core.Util

Your assembly should appear as follows (click the image for a larger version):

CCNET plugin assembly

Adding ISourceControl

The next step is to add the ISourceControl interface and provide the necessary attribute which will declare the name used to identify your Source Control Provider.

[ReflectorType("ftpscc")]
public class FTP: ISourceControl

The ReflectorType attribute comes from the NetReflector assembly and is located in the Exortech.NetReflector namespace. The value provided to the attribute is the name which will be used in ccnet.config as follows:

<sourcecontrol name="ftpscc"></sourcecontrol>

Adding FTP Specific Properties

Next, add properties for the FTP connection and once again we’ll use attributes from Exortech.NetReflector making it easy to declare settings from ccnet.config:

[ReflectorProperty("deleteRemoteFiles", Required = false)]
public bool DeleteRemoteFiles = true;

[ReflectorProperty(
"fileMask")]
public string FileMask;

[ReflectorProperty(
"localPath")]
public string LocalPath;

[ReflectorProperty(
"password")]
public string Password;

[ReflectorProperty(
"remoteHost")]
public string RemoteHost;

[ReflectorProperty(
"timeOut", Required = false)]
public int TimeOut = 60;

[ReflectorProperty(
"userName")]
public string UserName;

[ReflectorProperty(
"remoteDir", Required = false)]
public string RemoteDir = "/";

These properties indicate settings for the FTP connection as well as the location where the files should be placed after being downloaded. Additionally, there is a flag which indicates if the downloaded file should be deleted from the server.

Again, simply adding the ReflectorProperty attribute will cause these members to be populated with the data from your <sourcecontrol name="ftpscc"> tag in ccnet.config.

Implementing ISourceControl

Next, implement the ISourceControl interface on your class. Of course, the easiest way (in VS.NET) to stub out the necessary methods is via the context menu with the caret sitting on ISourceControl:

VS.NET Implement Interface

This will give you five new methods though I’ll focus on the following two since that’s all you really need to implement here:

public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)

public void GetSource(IIntegrationResult result)

In GetModifications we need to return an array which contains information about the files to be downloaded and in GetSource actually fetch the files. One caveat here is we use DateTime.Now as the modified time so that CCNET will consider the file as a “new” change. So, GetModifications will look like this:

1 public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)
2 {
3 ArrayList mods = new ArrayList();
4 Connect();
5 try 6 {
7 Log.Debug("Fetching list of files that match: " + FileMask);
8 FTPFile[] mModifiedFiles = ftp.DirDetails(FileMask);
9 Modification mod;
10 int i = 0;
11 foreach (FTPFile f in mModifiedFiles)
12 {
13 mod = new Modification();
14 mod.Type = "Added";
15 mod.FileName = f.Name;
16 mod.ChangeNumber = i;
17 mod.ModifiedTime = DateTime.Today;
18 mods.Add(mod);
19 i++;
20 }
21 return (Modification[])mods.ToArray(typeof(Modification));
22 }
23 finally 24 {
25 // Close the connection if there are no files to process 26 if (ftp.IsConnected && mods.Count == 0)
27 ftp.Quit();
28 }
29 }
30

GetSource handles downloading files from the server and then deleting them:

1 public void GetSource(IIntegrationResult result)
2 {
3 try 4 {
5 foreach (Modification m in result.Modifications)
6 {
7 Log.Debug("Fetching file: " + m.FileName);
8 ftp.Get(LocalPath, m.FileName);
9 if (DeleteRemoteFiles)
10 {
11 Log.Debug("Deleting remote file: " + m.FileName);
12 ftp.Delete(m.FileName);
13 }
14 result.AddTaskResult(string.Format(
15 "<fileDownloaded>{0}</fileDownloaded>\n", m.FileName));
16 }
17 }
18 finally 19 {
20 ftp.Quit();
21 }
22 }

Connecting to the FTP Server

The call to Connect in GetModifications above looks like this:

1 public void Connect()
2 {
3 Log.Debug(String.Format("Connecting to remotehost: {0}...", RemoteHost));
4 ftp.Connect();
5 Log.Debug(String.Format("Connected, logging in with user: {0}", UserName));
6 ftp.Login(UserName, Password);
7 Log.Debug(String.Format("Switching to remote directory: {0}", RemoteDir));
8 ftp.ChDir(RemoteDir);
9 }

The only thing I haven’t really covered here is the construction of the FTPClient member and the assignment of the its RemoteHost and Timeout properties which I think is pretty straightforward.

Conclusion

Writing plugins, whether for Source Control Providers or tasks (ITask), the process is pretty straightforward though there is little documentation on the subject. Be prepared to spend some time studying the other ISourceControl and ITask implementations included with CCNET and quite a bit of time debugging CCNET itself to understand how it works. One issue I’ve run into and that I’ve blogged about before which is when exceptions occur from within a Source Control Provider. In the current released version of CCNET (1.3) exceptions are not handled by the publishers for the project therefore you’ll never get notified about connection problems etc. unless you use the log4net settings I mentioned.

The above implementation is lacking in a number of areas, like error checking, and is a very simplified example of code I’m using in production. I’ve also tweaked CCNET itself so that any exceptions raised within my FTP provider are handled by the build publisher so that I can get email when things fail. In fact, I’ve been working to get these, or similar, changes incorporated into CCNET.

[UPDATE: Feb 6, 2008] Fix image links

TED Talks: Anand Agarawala and BumpTop desktop

I’m a fan of $g(TEDTalks) and this is one, about BumpTop desktop, is one I enjoyed.

There are a bunch of talks I’ve watched and I’m interested to know which ones you might have enjoyed. I’m looking for the best way to share these as I’m not sure an individual post about each one really makes sense.

In any case, I think the concepts presented are interesting though I’m not sure there all that practical on a desktop PC. As for practicality I think the iPhone/iPod Touch UI has some similar concepts which are very practical.

What are your favorites?

Copy, move and delete files using TortoiseSVN NOT Windows Explorer

Recently, I’ve been helping, or at least trying to help, a few people who are new to version control and SVN who’ve been having trouble. The problems range from folders not sync’ing with the server to files missing or having been deleted among others. Now, it’s not the fault of TortoiseSVN nor are these things specific to TortoiseSVN but I think it’s related to how well it integrates with Windows Explorer that leads to these issues.

If you’re a command line junkie this isn’t going to be of much use so continue right on browsing. But, if you do all your file manipulations, cut, copy, paste move etc. from within Windows Explorer then this is for you.

DO NOT…

  • Copy files using Explorer
  • Move files using Explorer
  • Delete files using Explorer
  • Drag/drop files using Explorer
image

If you have files checked into SVN then you need to use SVN commands to manipulate those files. One overlooked capability of TortoiseSVN is right click copy/move where you can right like folders in Explorer and drag them to a new location which will display this context menu (to the right) allowing you to select the operation of your choice. Whatever you do me sure that you’re using the TortoiseSVN commands and not messing around with files using the standard Explorer copy/move/delete otherwise you’ll be in for a world of hurt.