临时文档存放处,如果想围观我的话请移步到本人独立博客——http://besteric.com

How to Unit Test - WatiN, MbUnit and ASP.net

上一篇 / 下一篇  2009-09-15 14:57:36 / 个人分类:蛋疼的自动化

转载于:http://blog.benhall.me.uk/2007/12/how-to-unit-test-watin-mbunit-and.html

如果打不开请用国外代理


Today, I am going to be looking at WatiN, which stands for Web Application Testing in .Net, and seeing how well it actually works with an ASP.net web application.

WatiN is a type of record and playback framework to allow for automating the UI testing for web applications.  It uses IE and allows you to interact with the page, for example clicking buttons and typing in text.

A simple example from their documentation shows how to search for WatiN on google and assert you got the correct results back.

[Test]
public void SearchForWatiNOnGoogle()
{
using (IE ie = new IE("http://www.google.com"))
{
  ie.TextField(Find.ByName("q")).TypeText("WatiN");
  ie.Button(Find.ByName("btnG")).Click();
  Assert.IsTrue(ie.ContainsText("WatiN"));
}
}

You can use any test framework to get started, with MbUnit you simply write the tests as normal, but in your test fixture attribute you need to set the ApartmentState to STA. ApartmentState is part of .Net which states how the thread should execute. STA means that the thread will create and enter a single threaded apartment, everything will just be kept on the single thread.

[TestFixture(ApartmentState = ApartmentState.STA)]

If you don't have this set, when you run your tests you will receive the following exception:

Message: The CurrentThread needs to have it's ApartmentState set to ApartmentState.STA to be able to automate Internet Explorer.

Type: System.Threading.ThreadStateException
Source: WatiN.Core
TargetSite: Void CheckThreadApartmentStateIsSTA()

Now we have running tests, we can start to look at testing a 'real' application. Below is the screenshot of the application under test (AUT).

image

Entering information would cause the page to look like this:

image

Very simple.  To test this worked correctly, we would want to simulate what the user is doing and to verify it worked the same way a person would, in this case that it displayed the search term typed into the text box.

First we need to find the text box (called searchText) by it's name and enter a string.  We then find the button (by its value - the text displayed to the user and not the ID, the button ID is in fact Button1) and click it.  We then verify the response.

[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen()
{
    IE ie = new IE("http://localhost:49992/WatiNSite/");

    // Find the search text field and type Watin in it.
    ie.TextField(Find.ByName("searchText")).TypeText("WatiN");

    // Click the search button.
    ie.Button(Find.ByValue("Search")).Click();

    //Verify it contains search term.
    bool result = ie.Text.Contains("WatiN");
    Assert.IsTrue(result);
}

The URL points to the ASP.net development server for the solution, with a static port set.

image

That works with simply html, but what happens when ASP.net is pain with the naming. When using a master page with ASP.net, you place all of the pages content within a ContentPlaceHolder. When ASP.net renders this, to ensure naming of elements does not clash it prefixes the IDs.

Without using the placeholder, the textbox html is:

<input name="searchText" type="text" id="searchText" />

However, after using master pages it becomes:

<input name="ctl00$ContentPlaceHolder1$searchText" type="text" id="ctl00_ContentPlaceHolder1_searchText" />

Not great! All the elements are referred as strings, by moving to master pages all of our tests would break. WatiN will report this as:

Message: Could not find a 'INPUT (text password textarea hidden) or TEXTAREA' tag containing attribute name with value 'searchText'

One solution has been provided by James Avery in his WatiN Test Pattern post.  Here, he basically says that each page in the website should have a adapter in the test code which we code against which in turn calls the page (this is not mocking the page).  It means that changes to the website, such as naming, requires only changing the adapter class to match.  Following his pattern, the test would be:

[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen2()
{
    Default page = new Default("http://localhost:49992/WatiNSite/");

    page.SearchText.TypeText("WatiN");

    page.SearchButton.Click();

    bool result = page.Text.Contains("WatiN");
    Assert.IsTrue(result);
}

Its a lot easier to read as everything is referred to as the page and you can easily imagine what is happening.  The adapter looking like this:

public class Default : IE
{
    public TextField SearchText
    {
        get { return TextField(Find.ByName("searchText")); }
    }

    public Button SearchButton
    {
        get { return Button(Find.ByValue("Search")); }
    }

    public Default(string url) : base(url)
    {

    }
}

We could even take this a step on and abstract the searching into its own method which we call in the test.
    public void SearchFor(string term)
    {
        SearchText.TypeText(term);
        SearchButton.Click();
    }

The test would then be:

[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen3()
{
    Default page = new Default("http://localhost:49992/WatiNSite/");

    page.SearchFor("WatiN");

    bool result = page.Text.Contains("WatiN");
    Assert.IsTrue(result);
}

This does solve some of the problems as it makes the tests less dependent on the application code.  However, the ASP.net naming is still getting in the way. If we add another container or the element is inside a custom control we are still going to have a problem.  The answer is RegEx!

In our adapter, I change the way we find the element to use a method RegExName. Now its Find - ByName - RegExName - ElementName.

public TextField SearchText
{
    get { return TextField(Find.ByName(RegExName("searchText"))); }
}

Our RegExName method just takes in the elementname and return a regex which will ignore all of the ASP.net prefixes.

private Regex RegExName(string elementName)
{
    return new Regex(".*" + elementName + "$");
}

By using an adapter and RegEx we can abstract away from the real implementation and make our tests a lot more beneficial.  The tests are quite easy to write as long as you keep things focused. By arranging your tests as above, you should have a lot more long term success.

But having an automated test run for the UI will never replace a human tester as there are other scenarios to test for, cross browser, usability, wording which a test framework cannot test.
Technorati Tags: MbUnit, TDD, Testing, WatiN


TAG:

 

评分:0

我来说两句

Open Toolbar