Bulky Code

Problem
What should you do when the need to check many conditions results in deeply nested code?

Solution
Avoid nesting more than three levels when using conditional and looping constructs. A great resource on refactoring techniques for minimizing nesting is here.

Comments
Avoid deeply nested code because it’s difficult to understand and debug. Use static analysis tools to determine the cyclomatic complexity of your code.

Production-Quality Test Automation

Problem
You want to know the level of code quality required for automated test solutions and utilities.

Solution
Do not treat test automation as a full-fledged development project. Favor simplicity over production-grade code.

Comments
Strive to write maintainable test automation, but don’t sweat writing code that’s on the same level of the engineers building the product. Follow good programming practices, and prefer adding value to writing perfect code. In general, test automation is written for internal purposes only.

Exiting Loops Using Timeouts

Problem
On certain occasions, a test’s execution should only continue after a certain event. You’re using a looping construct to check for a condition, and you want to know what to do when it has been met.

Solution
Use timeouts to avoid infinite loops.

Comments
You may want to implement global timeouts in your project to reduce maintenance.

Example

// Method that checks the existence of a file.
bool FileExists() {
    ...
}

// Decrease timeout by 1 and wait for a second with each iteration.
// After 10 seconds, break out of the loop.
var maxWaitTime = 10;
while (!FileExists()) {
    if (maxWaitTime == 0) {
        break;
    }
    maxWaitTime--;
    Thread.Sleep(1000);
}

Sleeps and Pauses

Problem
Your test automation isn’t synchronizing well with the application under test (AUT). You need a way for your script to wait until the application is ready for the next action. Should you implement sleeps or pauses in your tests to overcome the problem? Thread.Sleep(), anyone?

Solution
As a general rule, you should not insert pauses or sleeps into your automation code. The best solution is to write a custom routine or leverage an existing library (e.g. Selenium WebDriver) to wait on a property of a UI element.

Comments
Sprinkling hard-coded sleeps in automation code isn’t desirable, because scripts will wait for a specified amount of time regardless of the application’s speed. And, as a result, they will take longer to execute.

Example

// Wrong. Note the sleep that waits 5 seconds for the page to refresh.
browser.FindElement(By.Id("submitButton")).Click();
System.Threading.Thread.Sleep(5000);
Assert.That(browser.FindElement(By.TagName("body")).Text.Contains("Order No:"));

// Correct. Explicitly wait for an element before proceeding.
browser.FindElement(By.Id("submitButton")).Click();
WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(5));
wait.Until(brw => brw.FindElement(By.Id("orderNo")));
Assert.That(browser.FindElement(By.TagName("body")).Text.Contains("Order No:"));

Record & Playback Automation

Problem
You want to know if it’s okay to use record and playback automation in real-world projects.

Solution
Avoid using record and playback to generate test automation code. Instead, manually develop an automation framework to support the tests you write.

Comments
In some scenarios, you may want to use a tool’s record feature to prototype a test or inspect how the tool is identifying UI elements.

Using the Obsolete Attribute

Problem
You have deprecated types or members, and you don’t want clients using them anymore. What should you do?

Solution
First, start by marking deprecated types and members with the Obsolete attribute. Do this in the first version released with the deprecated types. In subsequent versions, use the true argument of the Obsolete attribute to cause compile-time errors. This should force users to remove any reference to the member.

Comments
Use the Obsolete attribute’s Message property to specify what to use in place of the deprecated code. Also, don’t remove a type or member without marking it obsolete in at least one version of your library.

Example

// This usage causes a compilation warning.
[Obsolete("Use WaitForElement instead")]
void WaitForItem() {
    ...
}

// This usage causes a compilation error.
[Obsolete("Use WaitForElement instead", true)]
void WaitForItem() {
    ...
}

Serializable Attribute

Problem
You’re interested in knowing how to apply the Serializable attribute.

Solution
As a general rule, apply the Serializable attribute to all nonsealed classes. Yet, mark all non-serializable fields with the NonSerialized attribute.

Comments
This practice ensures that instances of a class, and all types that inherit from it, can be passed as arguments to remote methods.

The Conditional Attribute

Problem
You want to exclude a method and any statements that call it from a particular version of your application. How should you do it?

Solution
Prefer the Conditional attribute instead of the #if directive to exclude methods and all the statements that call them.

Comments
The Conditional attribute can abandon all statements that call a method, but it won’t discard the method definition itself. However, you can invoke the method via reflection.

Example

// This is incorrect.
#if BETAVERSION
    ShowSplashScreen();
#endif

void ShowSplashScreen() {
    ...
}

// This is correct. No need for an #if directive.
ShowSplashScreen();

[Conditional("BETAVERSION")]
void ShowSplashScreen() {
    ...
}