5.8. Automating the UI as an Object Model

Although the first approach is good for single isolated tests, it is not very scalable when it comes to full UI testing. The main problems are that the tests are difficult to read because they are very tight in controlling the browser and the UI — this means the actual test is hidden behind this. Secondly, if you need to make a change, then you will need to reflect this in a lot of different places. Because of the browser control nature, you have to duplicate a lot of the steps.

The second approach is to treat the UI as an object model. The aim is abstraction. You want to abstract away from the underlying browser control and from interaction with the UI framework, such as WatiN. Instead, you want your tests to be interacting with objects with meaningful names and methods that in turn interact with the UI framework. Next you'll see how the previous test could be rewritten taking the object model approach. Notice that there is no mention of the browser, buttons, click events, or waiting for pages to finish loading. Instead, you'll talk in terms of how Google works. You create an instance of a Google object, you set the search term, and you call the Search method. You can then verify that the result contains the text you expect:

[Test]
public void Search_Google_For_ASPNet()
{
    using (Google google = new Google())
    {
        google.SearchTerm = "asp.net";
        google.Search();
        string expectedString = "The Official Microsoft ASP.NET Site"
        Assert.IsTrue(google.Result.Contains(expectedString));
    }
}

In terms of how the Google object was implemented, you'll see that it takes exactly the same code as you had written before but splits it into reusable methods. For example, in the constructor you initial the browser as the following:

public Google()
{

ie = BrowserFactory.Create(BrowserType.InternetExplorer);
            ie.GoTo("http://www.google.com");
        }

In your search term and search method, you'll use exactly the same code that is used in previous examples, but provide more context to what it does by using a property and method:

public string SearchTerm
{
    set { ie.TextField(Find.ByName("q")).Value = value; }
}

public void Search()
{
   ie.Button(Find.ByName("btnG")).Click();
}

The advantage is improved readability and reusability. If Google changes, then you'll only have to change this layer and object model. As a result, your tests can remain untouched while your abstract object model changes to reflect this.

5.8.1. Creating UI Tests for Wrox Pizza

To demonstrate how to use the object model, you'll apply the patterns and concepts discussed in the Wrox Pizza store example. The aim is to create an object model and a test structure that will allow you to interact with the UI while achieving readable and maintainable tests.

Because you are interacting with the UI, it means you need to have the UI hosted and running. As such, you will take advantage of the Cassini pattern, as we discussed in the patterns section, to host the website. This is different from unit tests, because with unit tests you only interact with objects. With UI tests you are interacting with the actual application.

For this application, you are going to create a CassiniWebServer class, which will have the responsibility of starting and shutting down the web server. To do this, you will use the [TestFixtureSetUp] attribute, which is used to identify the method that should be executed before all the tests within that Test fixture are executed. As such, the website will be running before any tests are executed. When the tests have finished executing, the method with the attribute [TestFixtureTearDown] will be called. Here the test will stop and dispose of the server:

public abstract class CassiniWebServer
{
        [TestFixtureSetUp]
        public void Setup()
        {
            DirectoryInfo buildPath = new DirectoryInfo(Path
.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
            string serverStartPath = buildPath.Parent.FullName;
            string virtualDirectory = "/";
            int port = 8090;

CopyCassiniToBinDirectory(serverStartPath);

            try
            {
                server = new Server(port, virtualDirectory, serverStartPath);
                url = String.Format("http://localhost:{0}/", port);
                server.Start();

                Debug.WriteLine(String.Format("Web Server started. Port: {0}
Physical Directory: {1} @ {2}",
                    port, serverStartPath, URL));
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("Error starting web service {0}",
                   ex.Message));
                throw;
            }
        }
}

As you want all your tests to take advantage of this functionality, your tests will inherit from the CassiniWebServer class. Because you've defined the attributes then these will also be inherited and executed as part of your tests. As such, your Test fixture would be defined as the following:

[TestFixture]
public class List_Products_On_Menu_Tests: CassiniWebServer

With this in place, you can start interacting with the website.

5.8.2. Creating Tests for Listing Products

When you have the server in place you can start interacting with the website via test automation. The first page you are going to be testing is the product menu as shown in Figure 5-23. This is an important feature within the application because you want to tell people what you offer. If this doesn't work, then it doesn't matter what else does or doesn't work as no-one would be able to progress past this.

The first issue you face when creating your tests is naming. With unit tests you could name the tests based on the particular class and method you were thinking about. With UI automation you need to name your tests differently because you are working at a higher level. We always name the tests based on the feature that is currently under test. For example, one of the test classes for the page would be called List_Products_On_Menu_Tests.

An example of a name for an actual test would be Products_Menu_Displays_All_Products_On_Webpage. This will cover the scenario and ensure that the page correctly contains all the products. Notice you are not testing that they are displaying correctly or in the correct position. Instead, you just want to ensure the data is being returned and rendered in some form.

Figure 5-23. Wrox Pizza products page with test data created using Red Gate SQL Data Generator

At this point you can start writing your test. If you didn't follow the object model approach, your test would look like this:

[Test]
public void Products_Menu_Displays_All_Products_On_Webpage()
{
   using (IBrowser ie = BrowserFactory.Create(BrowserType.InternetExplorer))
   {
       ie.GoTo(URL);
       var link = ie.Link(Find.ByText("Menu"));
       link.Click();
       Assert.IsTrue(ie.ContainsText("Thrufropaquentor"));
   }
}

You'll start by first creating an instance of the browser and telling it to go to your URL. The URL is a variable set within the Cassini superclass when the server is started. After this, you'll need to link your menu to navigate to the correct section of your site to get to the particular page in question. After you click the link as a user would, you'll verify that a product name was returned in the text.

However, this test has a number of problems. First of all, what you are actually testing is hidden behind a lot of browser interactions, which makes it more difficult to read. If you change the name of the menu, all your tests will break, which is far from ideal. Finally, you have this random product name within your test that you're expecting to be there and you're expecting that the data will be in the database in the correct order every time. As a result, if anyone removes this item from the database then your tests will start to fail, even if no changes have been made to the code base. These types of problems are extremely difficult to identify and track down. The final problem is that when you're reading the test, you come across this magic string, which will be confusing and will not actually relate to anything with no indication as to how it got there. During the next sections we explain how you can solve these problems and make the tests easier to write, maintain, and read.

The first problem to solve is the fact that your tests are interacting directly with the browser instead of an object model as with the previous Google example.

The first part to model is the actual ProductsMenuPage itself:

public class ProductsMenuPage
{
    IBrowser Browser;
    public ProductsMenuPage(IBrowser browser)
    {
         Browser = browser;
    }
}

When this is constructed, you provide a browser instance that the object should use. The next thing you need to do is have access to the Menu link and the ability to click it. Because the same menu crosses multiple, different pages, you'll want to represent this within your tests.

As such, you'll create a Navigation object that will be responsible for maintaining navigation between different pages on your top menu bar. This object will have one method per link:

public class Navigation
{
        public Navigation(IBrowser browser)
        {
            Browser = browser;
        }     public ILink MenuPage
        {
            get {return GetLinkByText("Menu");}
        }

        private ILink GetLinkByText(string linkText)
        {
            return Browser.Link(Find.ByText(linkText));
        }

}

To improve readability, each page object model (e.g ProductsMenuPage) will inherit from the navigation object. In the constructor of ProductsMenuPage object, you can then click the link to navigate the browser:

public class ProductsMenuPage: Navigation
{

public ProductsMenuPage(IBrowser browser): base(browser)
        {
            MenuPage.Click();
        }
}

Note that you are also calling the base constructor of the Navigation object, therefore, the browser to allow us to identify the links.

The final stage after clicking the link is to verify that the correct text was displayed. Based on this, you simply need to request the Text from the browser and then expose it via a property:

public string Text
{
    get { return Browser.Text; }
}

However, you might want a more focused test to say that the product should appear where the products should be displayed and ensure the correct DIV tag. As such, you could have a different property that will only return the text that appears within the DIV based on the class assigned to it, which in this case is Products:

public string Products
{
    get { return Browser.Div(Find.ByClass("Products")).Text; }
}

You can now write your test based on your object model. Because you have the abstraction, your test is very streamlined. You simply need to initial the ProductsMenuPage object and then assert the response, as the click occurred within the constructor:

[Test]
public void Products_Menu_Displays_All_Products_On_Webpage()
{
    ProductsMenuPage page = new ProductsMenuPage(Browser);
    Assert.AreEqual("Thrufropaquentor", page.Text);
}

To improve the readability, additional logic related to opening and closing the browser should be abstracted into yet another superclass. As a result you now have an additional class which inherits from our CassiniWebServer:

public class BaseUITest: CassiniWebServer

Your TestFixture will inherit from the new BaseUITest class instead of the CassiniWebServer:

[TestFixture]
public class List_Products_On_Menu_Tests: BaseUITest

Within the BaseUITest, you take advantage of the methods attributed with [Setup] and [Teardown]. The setup and teardown methods are called before and after every test is started.

Now you can start the browser and point it at the homepage for the server:

public void BrowserSetup()
{
    Browser = BrowserFactory.Create(BrowserType.InternetExplorer);
    Browser.GoTo(URL);
}

Once we are done, we need to dispose the browser object which will close the browser logo.

public void BrowserTeardown()
{
    if(Browser != null)
        Browser.Dispose();
}

By this logic, in a superclass your tests don't need to be as concerned about ensuring that all the house-keeping activity needed to run the test has been done correctly. Instead, it is taken care of for you by the superclass.

You now have a set of structured classes. Our ProductsMenuPage object is the wrapper around our page and WatiN. This inherits from Navigation. In terms of tests, the List_Products_On_Menu_Tests inherits from BaseUITest which inherits from CassiniWebServer.

The final problem you have with the test is the magic string of the product you are expecting to be listed. So far you've also only tested one problem that appears, rather than all the products. From the test, you don't know if it is just that the test data only has one product or if the test is lacking in verification that it works.

To solve this problem, you want the tests to be in control of creating the test data.

5.8.3. Producing Test Data Using Test Data Builders

Test data builders is a pattern and approach you can take when you need to produce your test data for your tests. The aim is to use the data builder class to generate your objects that are pre-populated with known values that you can then use to verify that the system works as expected.

Product p = new Product();
p.BasePrice = 1.00;
p.Category = new ProductCategory { Description = "category" };
p.Description = "desc";
p.Name = "name";
p.PizzaTopping = true;

Instead of using the previous code, we could use the data builder that would look something like the following code. The advantage is that this can be designed to contain additional topics to help create the product object:

Product product = new ProductBuilder()
               .WithName("name")
               .WithDescription("desc")
               .WithPizzaTopping()

.InCategory("category")
               .WithBasePrice(1.00);

This approach of chaining method calls together is known as creating a fluent interface. The aim of a fluent interface is to improve the readability of the code to make it into a more natural language. As covered in Chapter 2, Rhino Mocks takes advantage of fluent interfaces when defining stubs or mocks.

To build a fluent interface and test data builder, we first need a builder object — in this case ProductBuilder. However, if you notice, your ProductBuilder can be assigned to a variable of type Product. This is the key to the approach. You can use a separate object to return your constructed object. You might expect that WithBasePrice() actually does the construction and returns the Product object, however with fluent interfaces you are never sure which method will be called last or in what order. Some people would include a .Build() method at the end; however, this harms readability and usability of the API. The answer is that C# has a special method which you can define on a class, in this case ProductBuilder, which will be executed when the object is attempted to be stored in a variable. As such, at this point you can create your Product class and return it to be stored in the variable correctly. The method looks like this:

public static implicit operator Product(ProductBuilder builder)
{
     return builder.Build();
}

On your builder, you have a private method called Build that knows how to correctly construct the object. Notice there is an additional logic based on initializing the category:

private Product Build()
{
       Product p = new Product { BasePrice = _price, Category = _category,
       Description = _desc, Name = _name, PizzaTopping = _pizzaTopping };

       p.Category.Products = new List<Product>();
       p.Category.Products.Add(p);
       return p;
}

You can now use your ProductBuilder within your tests to insert known data into the database, which you can then use to verify that the UI worked as expected. To configure the builder, you create a set of methods to configure certain properties to be used during the Build method above. The method WithName simply stores the parameter in a local field. In order to provide the fluent nature, it also returns the builder object for other methods to use and call methods on:

public ProductBuilder WithName(string name)
{
     _name = name;
     return this;
}

As such, you'll need to have some logic involved about how to access the database. Because these are UI tests, you can take advantage of the existing responsibilities you created in Chapter 3. These have already been tested and you've verified that they work as expected, meaning you can accept this dependency. Other people prefer to bypass the ORM and communicate directly with the database using a different approach such as ADO.NET.

As such, you'll now extend the BaseUITest class to support this new functionality and allow all your subtests to take advantage. Similar to your integration tests in Chapter 4, you are going to need to create an additional object called Database to handle the creating and dropping of the database itself. The reason you want to drop and re-create the database is to be sure that you are always running against a clean database with clean data.

Because you are taking advantage of NHibernate, you can use your existing DefaultNHibernateConfig object to create your scripts, which will create your tables:

public void CreateTables()
{
    string[] script = db.GenerateSchemaCreationScript();
    executeScripts(script, db.GetCurrentSession().Connection);
}

Unlike your integration tests, the UI tests will be running against a real SQL Server 2005 instance. As such, you will also need to drop the tables after you've finished:

public void DropTables()
{
     string[] script = db.GenerateDropSchemaScript();
     executeScripts(script, db.GetCurrentSession().Connection);
}

The next stage is to create your new database object and call the CreateTables() method within your [SetUp] method:

db = new Database();
db.CreateTables();

Finally, within your [TearDown] method, you should call the drop tables method.

The final stage is to insert the data into the newly created tables. Because you are creating your tables for each test, you also need to do that for each database. Sadly, for a Test fixture you cannot have more than one Setup or Teardown attribute, which you've already used in the BaseUITest. The workaround is to use abstract and virtual methods that could then be overloaded in your actual TestFixture class. Within our BaseUITest we define a virtual method without any implementation:

public virtual void DataCreation()
{}

Within our [SetUp] method you would call this function after creating your tables:

[SetUp]
public void BrowserSetup()
{
    db = new Database();
    db.CreateTables();
    DataCreation();
    Browser = BrowserFactory.Create(browserType);
    Browser.GoTo(URL);
}

This can then be overridden within your TestFixture to insert data targeted for that particular test while being executed within your Setup method:

public override void DataCreation()
{
        Product product = new ProductBuilder()
                                      .WithName("diff")
                                      .WithDescription("DiffDesc")
                                      .InCategory("diffCategory")
                                      .WithBasePrice(2.00);
        ProductRepository repository = new ProductRepository(DbConfig);
        repository.SaveOrUpdate(product);
}

As was mentioned before, you are taking advantage of your existing NHibernate mapping and code to insert the product into the database.

After you have this ProductBuilder in place, you can extend it to allow a list of products to return. For example, you could have the following which will produce four different products with different names and some different descriptions:

List<Product> products = new ProductBuilder()
    .WithNames("name", "name2", "name3", "name4")
    .WithDescriptions("default", "desc2", "desc3")
    .WithPizzaTopping()
    .InCategory("category")
    .WithBasePrice(1.00);

Alternatively, you could have the same product returned 10 times; each with a different ID:

List<Product> products = new ProductBuilder()
         .WithName("name")
         .WithDescription("desc")
         .InCategory("category")
         .WithBasePrice(1.00)
         .Times(10);

Again, this is done by taking advantage of the implicit operator method. Instead of just building a single product, you are building up a list of different products:

public static implicit operator List<Product>(ProductBuilder builder)
{
    builder.SetCount();
    List<Product> products = new List<Product>(_count);
    for (int i = 0; i < _count; i++)
    {
       products.Add(builder.Build());
    }

    return products;
}

At this point we can also include some additional logic to handle providing different names and descriptions for our products.

List<Product> products = new ProductBuilder()
               .WithNames("name", "name2", "name3", "name4")
               .WithDescriptions("default", "desc2", "desc3")
               .WithPizzaTopping()
               .InCategory("category")
               .WithBasePrice(1.00)
               .Times(10);

When creating the products, the first four products will be named name, name2, name3, name4. As we are producing ten products, the remaining six will be given the name of the first parameter — in this case simply name. This is the same for description.

This is where the power of the test data builder comes in as you have more control over how you build your objects.

5.8.4. Using Known Data to Verify

Now that you have your known product being inserted into your database, you can use this to verify that it will appear on the page:

[Test]
public void Products_Menu_Displays_All_Products_On_Webpage()
{
    ProductsMenuPage page = new ProductsMenuPage(Browser);
    page.Products.ShouldContain("p1");
}

However this is still only testing the fact that one product is returned. Instead, you'll want to insert a number of different products into the database:

public override void DataCreation()
{
    List<Product> products = new ProductBuilder()
        .WithNames("p1", "p2", "p3", "p4")
        .WithDescriptions("d1", "d2", "d3", "d4")
        .WithPizzaTopping()
        .InCategory("category")
        .WithBasePrice(1.00);

    ProductRepository repository = new ProductRepository(DbConfig);
    foreach (var product in products)
        repository.SaveOrUpdate(product);
}

Your test can then verify that all four products were correctly displayed on the screen and provide you with a lot more confidence that it is working as you expect it to:

[Test]
public void Products_Menu_Displays_All_Products_On_Webpage()

{
    ProductsMenuPage page = new ProductsMenuPage(Browser);
    page.Products.ShouldContain("p1");
    page.Products.ShouldContain("p2");
    page.Products.ShouldContain("p3");
    page.Products.ShouldContain("p4");
}

One thing you might notice from these tests is that you're moving away from Assert.Equal() as in previous tests. In an attempt to improve readability, in these examples you'll take a more BDD and RSpec approach to writing your tests.

The method ShouldContain() is an extension method. Extension methods allow you to extend classes by including a namespace containing static methods, marked as extensions. It works in exactly the same way, but we find it slightly more readable in the fact that is it saying the string "Products" should contain "p1." These extension methods were created as part of the machine.specification framework, which can be found on github as part of the machine project (http://github.com/machine/machine.specifications/tree/master).

When you have your tests, the one question left to ask is when and how often they should be executed. We feel that manually initiating the running of UI tests should only happen when you are concerned that you may have broken something. Otherwise, they should be running asynchronously, after the integration build has successfully finished on your continuous integration server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.205.99