public static void ParseArgs (string [] args) { // Arguments are assumed to come in pairs, e.g.: // program /arg1 arg_value1 /arg2 arg_value2 Validate.True ("Number of arguments is even", 0 == (args.Length & 1)); MethodInfo method_info; string convert_method; object [] parameters = new object [1]; Type [] string_type = { typeof (String) }; // Get the type for where ever this is being called // There must be a better way to do this Type this_type = new StackTrace ().GetFrame (0).GetMethod ().ReflectedType; try { // Loop through each argument pair (i += 2), assumed format is: // program /arg1 arg_value1 /arg2 arg_value2 // Other formats are not supported. arg1, arg2, etc are assumed // to be static properties for this_type. for (int i = 0; i < args.Length; i += 2) { // Trim the leading slash on the property name Validate.True ("Arguments start with a slash(/)", args [i] [0].Equals ('/')); string prop_name = args [i].TrimStart ('/'); // Get the property associated with this argument. We don't // have an instance, so only static properties are appropriate. PropertyInfo pi = this_type.GetProperty (prop_name, BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.SetProperty | BindingFlags.Public); // If there is no match then just continue if (null == pi) continue; // Get the Setter mthod for this property method_info = pi.GetSetMethod (); // Conversion is based on the Convert class. // Here we try to assemble the name for the appropriate // method of Convert. Methods are of the form "To____(String)" // where "____" is the Type we are trying to convert the // string two. convert_method = "To" + pi.PropertyType.Name; // Get the Convert method MethodInfo m = typeof (Convert).GetMethod ( convert_method, string_type); // We will pass the current arg_value to the Convert method parameters [0] = args [i + 1]; // Invoke the convert method. The result will be passed to // the property's Setter method. parameters [0] = m.Invoke (typeof (Convert), parameters); // Invoke the setter method on this_type method_info.Invoke (this_type, parameters); } } catch (Exception ex) { new LoggedException (ex, "Failed to parse command line arguments").Log (); } }
Ramblings of a person writing code that thinks too much about everything and needs a place to put it all
Tuesday, March 16, 2010
Using Reflection to Automatically Parse Command Line Arguments
It's been a while since I wrote a real command line application (long before C# got reflection) and haven't had to parse command line arguments in quite some time. Here is my new ParseArgs() function using reflection. The basic assumption is that arguments are being used to set static Properties of the entry point class.
Labels:
C#,
Reflection,
Write-Less-Code
Wednesday, March 10, 2010
Automated Retries
I'm still working on building up an automated GUI testing framework. It turns out that many GUI interaction are not very reliable for a number of reasons (the time it takes a for a dialog to pop-up can vary from session to session, for example).
To solve this my framework will retry some operations until successful or some limit is reached. Of course I don't want to write the same retry logic over and over again, so I came up with this little library function that makes use of lambdas to keep it generic.
The function Try() takes a delegate that returns true when successful and false otherwise. It will keep calling action until it returns true or the limit is reached.
Here is an example of it being used:
I make a delegate (action) that calls the method that is going to get retried and tests the result to see if it was successful or not. In this case we are trying to find a UI Element by it's name. FindElementByName_() will return the element if successful or null if not. To use the AutomatedRetry we simple call Try() with the action delegate and the number of times to retry. Pretty cool, huh?
To solve this my framework will retry some operations until successful or some limit is reached. Of course I don't want to write the same retry logic over and over again, so I came up with this little library function that makes use of lambdas to keep it generic.
public class AutomatedRetry { public uint RETRY_FACTOR = 50; public bool Try (Func<bool> action, uint limit) { for (uint tries = 0; tries < limit; tries++) { if (action ()) return true; else { uint time = tries * RETRY_FACTOR; Log (" retry in {0} ms", time); Thread.Sleep ((int)time); } } return false; } ... }
The function Try() takes a delegate that returns true when successful and false otherwise. It will keep calling action until it returns true or the limit is reached.
Here is an example of it being used:
public AutomationElement FindElementByName ( AutomationElement root, string name) { AutomationElement elm = null; Func<bool> action = () => { elm = FindElementByName_ (root, name); return null != elm; }; AutomatedRetry trier = new AutomatedRetry (); trier.Try (action, RETRY_LIMIT); return elm; }
I make a delegate (action) that calls the method that is going to get retried and tests the result to see if it was successful or not. In this case we are trying to find a UI Element by it's name. FindElementByName_() will return the element if successful or null if not. To use the AutomatedRetry we simple call Try() with the action delegate and the number of times to retry. Pretty cool, huh?
Labels:
C#,
Write-Less-Code
Monday, February 22, 2010
Improving GUI Test Maintenance with a Data Driven Framework
One the problems with automated GUI testing is maintenance. Dialog titles change, buttons move, tabs change, etc. These changes generally break automated tests. To make tests maintenance easier I have been working on a test framework that includes JSON files which define UI elements. This almost certainly will not be the final definition, but I thought I'd throw it out there and see if I get comments back on the structure.
Here is an example of a definition for the Page Setup dialog in Notepad.
You can think of this structure as a hash table with key => value pairs. The main element is "Notepad Page Setup". This is the key we will use to reference the Page Setup dialog in our test code. The value, in this case, is a bunch more key => value pairs that define the GUI element. BTW this is not a complete definition of the Page Setup dialog.
As an example I'll explain the paper size drop down on the Page Setup dialog (launch Notepad and UISpy if you want to follow along). The paper size drop down is a Selection List and in our definition we have called it "Sizes". It's AutomationId is "1137" (from UISpy).
The "Sizes" list is made of many Selection Items. I have only include a few here: "Ledger", "A5", and "Letter". These are the names that will be used in the test code to refer to these elements. Notice the hash key ("Ledger") does not have to match the Name of the element name ("11x17").
Here are some general definitions:
Now if the title of the dialog changes or the keystrokes it launch it or the name of a paper size changes we don't have to modify the test code. Instead we just update the JSON definition and any tests using that definition will automatically get the new definition. All the test code remains unchanged 8]
Here is an example of a definition for the Page Setup dialog in Notepad.
"Notepad Page Setup": { Name: "Page Setup", Keystrokes: "%(fu)", SelectionLists: { "Sizes": { AutomationId: "1137", SelectionItems: { "Ledger": { Name: "11x17", }, "A5": { Name: "A5", }, "Letter": { Name: "Letter", } } } }, Fields: { "Header": { AutomationId: "30" }, "Footer": { AutomationId: "31" } } Buttons: { "Ok": { AutomationId: "1" } } }You will need UISpy to find the Name and/or AutomationId for UI elements.
You can think of this structure as a hash table with key => value pairs. The main element is "Notepad Page Setup". This is the key we will use to reference the Page Setup dialog in our test code. The value, in this case, is a bunch more key => value pairs that define the GUI element. BTW this is not a complete definition of the Page Setup dialog.
As an example I'll explain the paper size drop down on the Page Setup dialog (launch Notepad and UISpy if you want to follow along). The paper size drop down is a Selection List and in our definition we have called it "Sizes". It's AutomationId is "1137" (from UISpy).
The "Sizes" list is made of many Selection Items. I have only include a few here: "Ledger", "A5", and "Letter". These are the names that will be used in the test code to refer to these elements. Notice the hash key ("Ledger") does not have to match the Name of the element name ("11x17").
Here are some general definitions:
- Name:
- The name of the UI element (from UISpy). The Name or the AutomationId can be used to find UI elements.
- AutomationId:
- The AutomationId for the UI element (from UISpy)
- Keystroke:
- Key stroke pattern to open the UI element
- SelectionLists:
- Array of Selection List defintions. Selection Lists are UI element that allow the user to chose one or more items from a list, e.g. ComboBox, ListBox, and even a DataGridView.
- SelectionItem:
- The definition for an item in a SelectionList
- Fields:
- Test fields
- Buttons:
- Generally buttons, but could be other things the user might click
[Test] public void SetPageSize() { // Assume Notepad has already been opened ... // Open the Page Setup Dialog UiItem ui = UiItem.Open("Notepad Page Setup"); ui.SelectionLists["Sizes"].SelectionItems["Ledger"].Select(); ui.Buttons["Ok"].Click(); ... }
Now if the title of the dialog changes or the keystrokes it launch it or the name of a paper size changes we don't have to modify the test code. Instead we just update the JSON definition and any tests using that definition will automatically get the new definition. All the test code remains unchanged 8]
Thursday, February 11, 2010
Sitemap for Blogger When Using FeedBurner
So I signed up with FeedBuner for CoderDoWhat.com and it broke (sort of) the sitemap used by Web Master Tools. Normally in WMT I would use http://blog.coderdowhat.com/atom.xml for the sitemap, but when you signup for and configure FeedBurner, requests for atom.xml get redirected to http://feeds.feedburner.com/coderdowhat which is not what I want to use as my sitemap.
As an alternative to atom.xml you can use http://blog.yourdomain.com/feeds/posts/full as your sitemap URL in WMT. This is not redirected and works perfect.
As an alternative to atom.xml you can use http://blog.yourdomain.com/feeds/posts/full as your sitemap URL in WMT. This is not redirected and works perfect.
Monday, February 8, 2010
Implementing Join() for Arrays in C# Using Extension Methods
Microsoft seems pretty good at borrowing good ideas from others, so why is it that .NET still doesn't include a Join() method for IEnumerables? Luckily it's easy to add this functionality using extension methods. Here is a fairly simple implementation.
using System; using System.Collections; using System.Text; using System.Collections.Generic; namespace Helpers { public static class CollectionHelper { public static string Join(this IEnumerable list, string separator) { string text = ""; if (null == list) return text; foreach (object item in list) { text += String.Format( "{0}{1}", item.ToString(), separator); } int idx = text.LastIndexOf(separator); if (-1 < idx) { text = text.Remove(text.LastIndexOf(separator)); } return text; } } }You can include the Join() method by adding a using Helpers.CollectionHelpers statement to your source file. It looks something like this.
using System; using System.Collections; namespace MyAppSpace { using Helpers.CollectionHelper; public partial class SomeClass { ... ... public string ProcessData(IList<double> data) { List<double> new_data = new List<double>(); double value = 0; foreach (double d in data) { value += d new_data.Add(value); } return new_data.Join(','); // <= Extension method } } }
AutomationElement.SetFocus() is Unreliable
Microsoft's UiAutomation framework has been fairly useful in my quest to automate all our GUI testing. My previous solution involved Ruby, WinUser32Ruby, and RSpec (which was a fun solution), but I got tired of try to wrap Win32 with Ruby.
UiAutomation seemed to solve all my problems right up until I tried to bring an AutomationElement to the foreground with a call to AutomationElement.SetFocus(). It would randomly fail. About 90% of the time the window in question would pop up and the test would pass with flying colors. But every once in a while the test would fail. Some quick debugging lead me to question the reliability of SetFocus().
Rather than monkey around with SetFocus(), Full Trust, and the .NET Security Framework I decide to rely on good old Win32. For everyone who's being driven crazy by SetFocus(), all three of us, here is my win32 based SetForegroundWindow() method.
UiAutomation seemed to solve all my problems right up until I tried to bring an AutomationElement to the foreground with a call to AutomationElement.SetFocus(). It would randomly fail. About 90% of the time the window in question would pop up and the test would pass with flying colors. But every once in a while the test would fail. Some quick debugging lead me to question the reliability of SetFocus().
Rather than monkey around with SetFocus(), Full Trust, and the .NET Security Framework I decide to rely on good old Win32. For everyone who's being driven crazy by SetFocus(), all three of us, here is my win32 based SetForegroundWindow() method.
public bool SetForegroundWindow (AutomationElement elm, uint retries) { Util.ValidateNotNull ("elm", elm); try { if (retries < RETRY_LIMIT) { // Using Win32 to set foreground window because // AutomationElement.SetFocus() is unreliable // Get handle to the element IntPtr other = FindWindow (null, elm.Current.Name); // Get the Process ID for the element we are trying to // set as the foreground element int other_id = GetWindowThreadProcessId ( other, IntPtr.Zero); // Get the Process ID for the current process int this_id = GetWindowThreadProcessId ( Process.GetCurrentProcess ().Handle, IntPtr.Zero); // Attach the current process's input to that of the // given element. We have to do this otherwise the // WM_SETFOCUS message will be ignored by the element. bool success = AttachThreadInput (this_id, other_id, true); // Make the Win32 call IntPtr previous = SetForegroundWindow (other); if (IntPtr.Zero.Equals(previous)) { // Trigger re-try throw new Exception( "SetForegroundWindow failed"); } else { Log (" focus set"); } return true; } // Exceeded retry limit, failed! return false; } catch { retries++; uint time = retries * RETRY_FACTOR; Log (" Could not SetFocus(), retry in {0} ms", time); Thread.Sleep ((int)time); return SetForegroundWindow (elm, retries); } }This method relies on Win32 calls, so you must import the requisite Win32 functions.
using System.Runtime.InteropServices; ... [DllImport ("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr FindWindow ( string lpClassName, string lpWindowName); [DllImport ("user32.dll", CharSet = CharSet.Auto)] static extern bool AttachThreadInput ( int idAttach, int idAttachTo, bool fAttach); [DllImport ("user32.dll", CharSet = CharSet.Auto)] static extern int GetWindowThreadProcessId ( IntPtr hWnd, IntPtr lpdwProcessId); [DllImport ("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr SetForegroundWindow (IntPtr hWnd);
Labels:
C#,
Ui-Automation
Saturday, January 16, 2010
Waiting for Windows to Load in Automated GUI Testing
I recently started using Microsoft's UiAutomation framework to do some automated GUI testing. It's slightly better than using Win32 directly (subject of a separate post), but, like all things based in reality, leaves a little something to be desired.
The Ui Automation framework is built around the AutomationElement. The AutomationElement provides methods for finding child windows by their name (or title) and by Automation ID. This is great. With the help of UISpy it's very easy to find any element in an application (even ones with no title).
In the wonderful world of automated GUI testing you often cannot predict how long it will take for a particular UI element to load. Consequently, you are forever writing Thread.Sleep(3000). Not only does this get real old real fast, but it's not very robust since the continuous integration (CI) server running the tests could run significantly slower then your development box.
To solve this problem I wrapped AutomationElement.FindFirst() in a new method FindElementByName() and added some logic to handle the fact that the element may not be immediately available.
/// <summary> /// Find an element by its Title or Name /// </summary> /// <param name="root"> /// The root element to start searching from /// </param> /// <param name="name"> /// The Title or Name of the element to search for /// </param> /// <param name="retries"> /// The maximum number of times to retry if the element is /// not found /// </param> /// <returns></returns> public AutomationElement FindElementByName (AutomationElement root, string name, uint retries) { Util.ValidateNotEmpty ("name", name); Condition c = new PropertyCondition ( AutomationElement.NameProperty, name, PropertyConditionFlags.IgnoreCase); if (null == root) { root = AutomationElement.RootElement; } // First look for imediate children AutomationElement ae = root.FindFirst ( TreeScope.Element | TreeScope.Children, c); if (null == ae) { ae = root.FindFirst ( TreeScope.Element | TreeScope.Descendants, c); } // If we have not found the window by now then it may be slow in // opening, so we wait 'time' sec and try again. if (null == ae && retries < RETRY_LIMIT) { retries++; uint time = RETRY_FACTOR * retries; Log (" Element \"{0}\" not found, retrying in {1} ms", name, time); Thread.Sleep ((int)time); ae = FindElementByName (root, name, retries); } return ae; }
Labels:
C#,
GUI-Testing,
Testing,
Ui-Automation
Thursday, January 7, 2010
Validating Arguments
How many times have you seen (or, shame on you, written) this snippet of code to validate that an argument is not null?
public String StopDoingThis(MyRediculousObj foobar) { if (null == foobar) { throw new ArgumentNullException ( String.Format ("StopDoingThis: 'foobar' cannot be null", GetCallingMethod (), variable_name)); } foreach (Foo foo in foobar.InfiniteFoo()) { Console.WriteLine("Stop it! Please stop it!"); } return "Realy, please stop it!"; }
Why do coders insist on writing this same chunk of code over and over again? I know, it's obvious: "Coders are lazy."
But if we're so lazy why then hasn't every coder from New York to Thailand encapsulated this ever so common validation. I dunno, but here it is. Use it and stop writing this damn validation at the top of every freakin' method!
But if we're so lazy why then hasn't every coder from New York to Thailand encapsulated this ever so common validation. I dunno, but here it is. Use it and stop writing this damn validation at the top of every freakin' method!
public String NiceAndClean(FabulousObj sonice) { ValidateNotNull("sonice", sonice) return "Look how nice and clean that was :)"; } public void ValidateNotNull ( string variable_name, Object parameter) { if (null == parameter) { throw new ArgumentNullException ( String.Format ("{0}: '{1}' cannot be null", GetCallingMethod (), variable_name)); } } private string GetCallingMethod () { StackTrace stackTrace = new StackTrace (); // We get the second (2) frame. The first frame would // be the method ('Method B') calling GetCallingMethod(). // What the user really wants in the method that called // 'Method B', so we want the second frame. return stackTrace.GetFrame (2).GetMethod ().Name; }
If you're truely a lazy coder then I'd expect you to write a whole bunch of these: ValidateNotEmpty, ValidateBounds, ValidateThisThatAndTheOtherThing. They're quicker than cut and paste, easier to read, and make your code look better than the dude in the cube next to you :p
Labels:
C#,
Rant,
Write-Less-Code
Subscribe to:
Posts (Atom)