Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Create Hybrid Test Framework – Work with Config Files

5.00/5 (4 votes)
2 Apr 2017Ms-PL5 min read 4.4K  
Improve your test framework so that your tests don't depend on hard-coded numbers. Use configuration files and change the values without compilation.

Introduction

In the Design & Architecture Series, we talk about ways of creating maintainable test framework. In this article, I am going to elaborate on a topic that most people don't understand how important it is - configurations. Most engineers use hard-coded values directly in their code and experience later lots of problems when they try to execute their tests in different environments. I am going to show you how to get your configurations from app.config and change them without recompilation. In the next article from the series, I will show you how to change these settings based on the environment where the tests will be executed.

What Problem Are We Trying to Solve?

Below you can find a code that most people usually write. Hard-coding the numbers directly in their code so that they cannot change it easily per environment or build configuration. The best way of using such settings is reading them from the app.config file. Later, you can transform these values based on the build configuration. I will show you how to do that in the next article from the series.

C#
public sealed class BrowserSettings
{
    public BrowserSettings(Browsers type)
    {
        this.Type = type;
    }

    public static BrowserSettings DefaultChomeSettings
    {
        get
        {
            return new BrowserSettings(Browsers.Chrome)
            {
                BrowserExeDirectory = string.Empty,
                PageLoadTimeout = 60,
                ScriptTimeout = 60,
                ElementsWaitTimeout = 60
            };
        }
    }

    public static BrowserSettings DefaultFirefoxSettings
    {
        get
        {
            return new BrowserSettings(Browsers.Firefox)
            {
                BrowserExeDirectory = Environment.CurrentDirectory,
                PageLoadTimeout = 60,
                ScriptTimeout = 60,
                ElementsWaitTimeout = 60
            };
        }
    }

    public static BrowserSettings DefaultInternetExplorerSettings
    {
        get
        {
            return new BrowserSettings(Browsers.InternetExplorer)
            {
                BrowserExeDirectory = Environment.CurrentDirectory,
                PageLoadTimeout = 60,
                ScriptTimeout = 60,
                ElementsWaitTimeout = 60
            };
        }
    }

    public Browsers Type { get; private set; }

    public int ScriptTimeout { get; set; }

    public int PageLoadTimeout { get; set; }

    public int ElementsWaitTimeout { get; set; }

    public string BrowserExeDirectory { get; set; }
}

Work with Config Files C#

There are a couple of ways of reading these settings from the app.config file. I am going to show you two of them. The first uses a section that defines the different timeouts using XML attributes. The second uses different XML nodes.

Create Config Section with Attributes

First, we will go through the first variation that utilises XML attributes.

App.config

Under the configSections node, you need to register your new section. It should contain the name of the section (name attribute). Then you need to specify the full name of the C# class where the section is defined and its assembly name. Use the following format - FullNameSpace.NameOfTheClass, AssemblyName.

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="TimeoutSettingsAsAttributes" 
             type="HybridTestFramework.UITests.Core.Configuration.TimeoutSettingsAsAttributes, 
                   HybridTestFramework.UITests.Core"/>
  </configSections>
  <TimeoutSettingsAsAttributes 
    waitForAjaxTimeout="30000" 
    sleepInterval="1000" 
    elementToBeVisibleTimeout="30000" 
    elementToExistTimeout="60000" 
    elementToNotExistTimeout="10000" 
    elementToBeClickableTimeout="30000" 
    elementNotToBeVisibleTimeout="10000" 
    elementToHaveContentTimeout="30000"/>
<startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>

As you can see under the configSections node, you can find the new section - TimeoutSettingsAsAttributes. The different attributes represent our numbers that we will later use in the tests. Of course, you can use any C# type not only int - string, bool, etc.

TimeoutSettingsAsAttributes

To be able to read the numbers from the app.config file, you need to define a mapping C# class - TimeoutSettingsAsAttributes (the one that we specified under the configSection node). You need to add a reference and a using statement to System.Configuration. This is an assembly that comes from .NET framework. It contains the ConfigurationProperty attribute that we use here to map the XML attributes to C# properties. Also, your class needs to derive from ConfigurationSection. The first parameter of the ConfigurationProperty will direct the engine which XML attribute to read. You can also specify a default value and whether the property is required. Through this["nameOfTheAttribute"], the actual reading is happening.

C#
public class TimeoutSettingsAsAttributes : ConfigurationSection
{
    public TimeoutSettingsAsAttributes()
    {
    }

    [ConfigurationProperty("waitForAjaxTimeout", 
    DefaultValue = "10000", IsRequired = true)]
    public int WaitForAjaxTimeout
    {
        get
        {
            return (int)this["waitForAjaxTimeout"];
        }
        set
        {
            this["waitForAjaxTimeout"] = value;
        }
    }

    [ConfigurationProperty("sleepInterval", IsRequired = true)]
    public int SleepInterval
    {
        get
        {
            return (int) this["sleepInterval"];
        }
    }

    [ConfigurationProperty("elementToBeVisibleTimeout", IsRequired = true)]
    public int ElementToBeVisibleTimeout
    {
        get
        {
            return (int) this["elementToBeVisibleTimeout"];
        }
    }

    [ConfigurationProperty("elementToExistTimeout", IsRequired = true)]
    public int ?lementToExistTimeout
    {
        get
        {
            return (int) this["elementToExistTimeout"];
        }
    }

    [ConfigurationProperty("elementToNotExistTimeout", IsRequired = true)]
    public int ?lementToNotExistTimeout
    {
        get
        {
            return (int) this["elementToNotExistTimeout"];
        }
    }

    [ConfigurationProperty("elementToBeClickableTimeout", IsRequired = true)]
    public int ?lementToBeClickableTimeout
    {
        get
        {
            return (int) this["elementToBeClickableTimeout"];
        }
    }

    [ConfigurationProperty("elementNotToBeVisibleTimeout", IsRequired = true)]
    public int ?lementNotToBeVisibleTimeout
    {
        get
        {
            return (int) this["elementNotToBeVisibleTimeout"];
        }
    }

    [ConfigurationProperty("elementToHaveContentTimeout", IsRequired = true)]
    public int ?lementToHaveContentTimeout
    {
        get
        {
            return (int) this["elementToHaveContentTimeout"];
        }
    }
}

TimeoutSettingsAsAttributesProvider

The provider class is the one that we use in the tests to get the numbers from the app.config file. It contains static properties. In its static constructor, we read the XML section and deserialize it to our TimeoutSettingsAsAttributes C# class.

C#
public class TimeoutSettingsAsAttributesProvider : ConfigurationSection
{
    private static readonly TimeoutSettingsAsAttributes timeoutSettings;

    static TimeoutSettingsAsAttributesProvider()
    {
        try
        {
            timeoutSettings = 
                (TimeoutSettingsAsAttributes) 
                ConfigurationManager.GetSection
                (sectionName: "TimeoutSettingsAsAttributes");
        }
        catch (ConfigurationErrorsException ex)
        {
            throw new ConfigurationErrorsException(
                message: "Please configure correctly 
                the TimeoutSettingsAsAttributes section.",
                inner: ex);
        }
    }

    public static int WaitForAjaxTimeout
    {
        get
        {
            return timeoutSettings.WaitForAjaxTimeout;
        }
    }

    public static int SleepInterval
    {
        get
        {
            return timeoutSettings.SleepInterval;
        }
    }

    public static int ElementToBeVisibleTimeout
    {
        get
        {
            return timeoutSettings.ElementToBeVisibleTimeout;
        }
    }

    public static int ?lementToExistTimeout
    {
        get
        {
            return timeoutSettings.?lementToExistTimeout;
        }
    }

    public static int ?lementToNotExistTimeout
    {
        get
        {
            return timeoutSettings.?lementToNotExistTimeout;
        }
    }

    public static int ?lementToBeClickableTimeout
    {
        get
        {
            return timeoutSettings.?lementToBeClickableTimeout;
        }
    }

    public static int ?lementNotToBeVisibleTimeout
    {
        get
        {
            return timeoutSettings.?lementNotToBeVisibleTimeout;
        }
    }

    public static int ?lementToHaveContentTimeout
    {
        get
        {
            return timeoutSettings.?lementToHaveContentTimeout;
        }
    }
}

Create Config Section with Dedicated Nodes

The first approach using attributes is a little bit unreadable in my opinion. Because of that, I am going to show you the second one where we are going to use dedicated XML nodes.

App.config

First, we register the new section using the same approach. Then we create the TimeoutSettings but instead specifying the numbers using attributes. We use dedicated XML nodes that have the value attribute where the actual number is.

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="TimeoutSettings" 
     type="HybridTestFramework.UITests.Core.Configuration.TimeoutSettings, 
     HybridTestFramework.UITests.Core"/>
  </configSections>

  <TimeoutSettings>
    <waitForAjaxTimeout value="30000"/>
    <sleepInterval value="1000"/>
    <elementToBeVisibleTimeout value="30000"/>
    <elementToExistTimeout value="60000"/>
    <elementToNotExistTimeout value="10000"/>
    <elementToBeClickableTimeout value="30000"/>
    <elementNotToBeVisibleTimeout value="10000"/>
    <elementToHaveContentTimeout value="30000"/>
  </TimeoutSettings>
  
<startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>

TimeoutSettings

Again, we need to create the class that maps the section to C#. Again, we need a reference and a using statement to System.Configuration. The only difference compared to the TimeoutSettingsAsAttributes class is that the properties here are of type ValueConfigElement<int>. In short, this type describes our XML nodes and tells the engine that there is a value attribute where the number is.

C#
public class TimeoutSettings : ConfigurationSection
{
    public TimeoutSettings()
    {
    }

    [ConfigurationProperty("waitForAjaxTimeout")]
    public ValueConfigElement<int> WaitForAjaxTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["waitForAjaxTimeout"];
        }
    }

    [ConfigurationProperty("sleepInterval")]
    public ValueConfigElement<int> SleepInterval
    {
        get
        {
            return (ValueConfigElement<int>) this["sleepInterval"];
        }
    }

    [ConfigurationProperty("elementToBeVisibleTimeout")]
    public ValueConfigElement<int> ElementToBeVisibleTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementToBeVisibleTimeout"];
        }
    }

    [ConfigurationProperty("elementToExistTimeout")]
    public ValueConfigElement<int> ?lementToExistTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementToExistTimeout"];
        }
    }

    [ConfigurationProperty("elementToNotExistTimeout")]
    public ValueConfigElement<int> ?lementToNotExistTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementToNotExistTimeout"];
        }
    }

    [ConfigurationProperty("elementToBeClickableTimeout")]
    public ValueConfigElement<int> ?lementToBeClickableTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementToBeClickableTimeout"];
        }
    }

    [ConfigurationProperty("elementNotToBeVisibleTimeout")]
    public ValueConfigElement<int> ?lementNotToBeVisibleTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementNotToBeVisibleTimeout"];
        }
    }

    [ConfigurationProperty("elementToHaveContentTimeout")]
    public ValueConfigElement<int> ?lementToHaveContentTimeout
    {
        get
        {
            return (ValueConfigElement<int>) this["elementToHaveContentTimeout"];
        }
    }
}

ValueConfigElement

You can use the ValueConfigElement with any C# type. Here we derive from the ConfigurationElement class.

C#
public class ValueConfigElement<T> : ConfigurationElement
{
    public ValueConfigElement()
    {
    }

    [ConfigurationProperty("value")]
    public T Value
    {
        get
        {
            return (T) this["value"];
        }
    }
}

TimeoutSettingsProvider

To use the settings in tests, we need again a provider class. It is almost identical to TimeoutSettingsAsAttributesProvider with the only difference that in order to get the number, we call the Value property of the elements.

C#
public class TimeoutSettingsProvider : ConfigurationSection
{
    private static readonly TimeoutSettings timeoutSettings;

    static TimeoutSettingsProvider()
    {
        try
        {
            timeoutSettings = 
                (TimeoutSettings) 
                ConfigurationManager.GetSection(sectionName: "TimeoutSettings");
        }
        catch (ConfigurationErrorsException ex)
        {
            throw new ConfigurationErrorsException(
                message: "Please configure correctly the TimeoutSettings section.",
                inner: ex);
        }
    }

    public static int WaitForAjaxTimeout
    {
        get
        {
            return timeoutSettings.WaitForAjaxTimeout.Value;
        }
    }

    public static int SleepInterval
    {
        get
        {
            return timeoutSettings.SleepInterval.Value;
        }
    }

    public static int ElementToBeVisibleTimeout
    {
        get
        {
            return timeoutSettings.ElementToBeVisibleTimeout.Value;
        }
    }

    public static int ?lementToExistTimeout
    {
        get
        {
            return timeoutSettings.?lementToExistTimeout.Value;
        }
    }

    public static int ?lementToNotExistTimeout
    {
        get
        {
            return timeoutSettings.?lementToNotExistTimeout.Value;
        }
    }

    public static int ?lementToBeClickableTimeout
    {
        get
        {
            return timeoutSettings.?lementToBeClickableTimeout.Value;
        }
    }

    public static int ?lementNotToBeVisibleTimeout
    {
        get
        {
            return timeoutSettings.?lementNotToBeVisibleTimeout.Value;
        }
    }

    public static int ?lementToHaveContentTimeout
    {
        get
        {
            return timeoutSettings.?lementToHaveContentTimeout.Value;
        }
    }
}

Config Sections Usage in Tests

Usage Config Section with Attributes

To use the settings in tests, you need to call the static provider class - TimeoutSettingsAsAttributesProvider and call one of its properties. Of course, the project should contain the mentioned app.config file with the correct configurations.

C#
[TestMethod]
public void GetConfigValues_From_TimeoutSettingsAsAttributes()
{
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.WaitForAjaxTimeout),
        TimeoutSettingsAsAttributesProvider.WaitForAjaxTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.SleepInterval),
        TimeoutSettingsAsAttributesProvider.SleepInterval);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.ElementToBeVisibleTimeout),
        TimeoutSettingsAsAttributesProvider.ElementToBeVisibleTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.?lementNotToBeVisibleTimeout),
        TimeoutSettingsAsAttributesProvider.?lementNotToBeVisibleTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.?lementToBeClickableTimeout),
        TimeoutSettingsAsAttributesProvider.?lementToBeClickableTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.?lementToExistTimeout),
        TimeoutSettingsAsAttributesProvider.?lementToExistTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.?lementToHaveContentTimeout),
        TimeoutSettingsAsAttributesProvider.?lementToHaveContentTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsAsAttributesProvider),
        nameof(TimeoutSettingsAsAttributesProvider.?lementToNotExistTimeout),
        TimeoutSettingsAsAttributesProvider.?lementToNotExistTimeout);
}
Image 1

Usage Config Section with Dedicated Nodes

To use the settings in tests, you need to call the static provider class - TimeoutSettingsProvider and call one of its properties. Of course, the project should contain the mentioned app.config file with the correct configurations.

C#
[TestMethod]
public void GetConfigValues_From_TimeoutSettings()
{
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.WaitForAjaxTimeout),
        TimeoutSettingsProvider.WaitForAjaxTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.SleepInterval),
        TimeoutSettingsProvider.SleepInterval);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.ElementToBeVisibleTimeout),
        TimeoutSettingsProvider.ElementToBeVisibleTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.?lementNotToBeVisibleTimeout),
        TimeoutSettingsProvider.?lementNotToBeVisibleTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.?lementToBeClickableTimeout),
        TimeoutSettingsProvider.?lementToBeClickableTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.?lementToExistTimeout),
        TimeoutSettingsProvider.?lementToExistTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.?lementToHaveContentTimeout),
        TimeoutSettingsProvider.?lementToHaveContentTimeout);
    Console.WriteLine("From {0}- {1} = {2}",
        nameof(TimeoutSettingsProvider),
        nameof(TimeoutSettingsProvider.?lementToNotExistTimeout),
        TimeoutSettingsProvider.?lementToNotExistTimeout);
}

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)