标签:des winform style blog class code
在Visual Studio中,Coded UI Test已经不是什么新特性了,较早版本的Visual Studio中就已经有这个东东了。它主要用来帮助自动化测试工程师和开发人员确保程序在UI方面没有任何问题。这其中包含了丰富的内容。在这之前,我一直对自动化测试的工作以及什么是自动化测试一知半解,具备自动化测试编码能力的工程师所掌握的技能在某种程度上要远超程序开发人员和设计人员,对于这一点,我早有耳闻!但直到亲身体验我才确信,测试工作远没有我们想象得那么简单。开发人员或许花上数小时就可以完成项目中某一个独立模块并使其在一定范围内正常运行,然而,自动化测试工程师也许会花上好几天的时间来编写对应的自动化测试代码来确保这一功能运行正常。
Coded UI Test包含了十分丰富的API库,它可以录制和回放UI操作,捕捉UI元素并获取属性的值,并生成操作代码。测试人员在生成代码的基础上对测试对象的值进行逻辑判断并给出测试结果。创建一个Coded UI Test很容易,大多数情况下,我们只需要借助于Visual Studio就可以完成绝大部分操作。为了说明整个操作过程,我们假设一个测试需求:
在浏览器中打开百度搜索,输入“jaxu cnblogs”关键字,搜索并查看结果的第一条是否为“Jaxu - 博客园”
基本操作
(本文演示的所有代码和操作均在Visual Sutdio 2013和Windows 8.1 + IE 11环境下)
在Visual Studio中开始创建一个Coded UI Test Project。这很简单!
工程创建成功后,Visual Studio会问你是马上开始一个新的UI录制还是选择已经录制好的操作。当然你也可以选择取消,在后面的步骤里再开始UI录制。
工程默认生成CodedUITest1.cs文件。在开始录制UI操作之前,对基本概念做一下介绍:
然后,我们开始一个UI录制。在工程中添加一个Coded UI Test Map文件。创建成功后Visual Studio会自动在屏幕的右下角打开Coded UI Test Builder窗口,以方便我们进行UI录制操作。
借用MSDN上的图片来对Coded UI Test Builder窗口上按钮的功能做一下简单的说明:
UI Action的录制和UI控件的选择操作是分开的。让我们先开始UI Action的录制。
在Solution Explorer中展开UIMap1.uitest文件,选择并打开UIMap1.Designer.cs文件,可以看到刚才所生成的代码。是不是很想现在就运行一下,来看看这些自动生成的代码如何运行?现在还不行,因为单纯的UI Action运行没有任何意义,Coded UI Test的真正意义是通过UI操作来定位到UI上的某一个特定元素,并最终通过断言来确定该元素的属性是否和预期的值相等。
为了能够手动修改.Designder.cs文件中生成的代码,我们需要将它们移到.cs文件中。在Solution Explorer中双击UIMap1.uitest文件,在打开的窗口中我们可以看到左边是UI Actions所生成的步骤,右边是UI Control Map(稍后我们会用到它)。在左边的UI Actions中选择根节点RecordedMethod1,然后在顶部的菜单中选择Move code to UIMap1.cs,代码会被移到.cs文件以方便我们进行修改。完成该步骤之后,我们可以在.cs文件中看到这些代码并做相应的修改。
你可能已经注意到了,自动生成的代码中有些对象的名字看起来并不那么好,甚至有些还包含了中文。你希望修改它们,但是不要在.Designer.cs文件中做任何修改!还记得前面我们讲过的Edit With Coded UI Test操作吗?在Solution Explorer中右键选择UIMap1.uitest文件,右键选择Edit With Coded UI Test打开Coded UI Test Builder窗口,然后点击Add assertions按钮(就是那个用来选择UI Control的按钮),然后展开UI Control Map界面。如下图,我们可以对其中生成的UI Controls进行编辑和重命名。
完成修改之后再次点击Generate code按钮并关闭Coded UI Test Builder窗口,此时.Designer.cs文件中自动生成的代码已经做了修改。由于前面我们已经将相关的UI Actions部分的代码移到.cs文件里了,所以重命名的对象我们还需要在.cs文件中手动进行修改,否则编译时会出错。建议在将代码移到.cs文件之前完成自动生成代码的修改工作,以避免手动修改过多的代码。
然后我们需要捕捉到百度搜索结果的UI控件,并对其中的结果进行判断。仍然使用Coded UI Test Builder窗口。
至此,所有的UI Actions和UI Controls都已经定义完毕,接下来我们要编码以完成对搜索结果的判断。借助于自动生成的代码,我们编写了下面的测试方法以实现文章最开始的测试需求。
namespace CodedUITestProject2.UIMap1Classes { using Microsoft.VisualStudio.TestTools.UITesting.HtmlControls; using Microsoft.VisualStudio.TestTools.UITesting.WinControls; using System; using System.Collections.Generic; using System.CodeDom.Compiler; using Microsoft.VisualStudio.TestTools.UITest.Extension; using Microsoft.VisualStudio.TestTools.UITesting; using Microsoft.VisualStudio.TestTools.UnitTesting; using Keyboard = Microsoft.VisualStudio.TestTools.UITesting.Keyboard; using Mouse = Microsoft.VisualStudio.TestTools.UITesting.Mouse; using MouseButtons = System.Windows.Forms.MouseButtons; using System.Drawing; using System.Windows.Input; using System.Text.RegularExpressions; public partial class UIMap1 { public void TestSearchResult() { HtmlDiv resultPanel = this.UINewtabInternetExplorWindow.UIJaxucnblogs_SearchDocument.UIContent_leftPane; HtmlDiv resultPanelFirst = (HtmlDiv)resultPanel.GetChildren()[0]; HtmlHyperlink link = new HtmlHyperlink(resultPanelFirst); Assert.AreEqual("Jaxu - 博客园", link.InnerText, "Validation is failed."); } /// <summary> /// RecordedMethod1 - Use ‘RecordedMethod1Params‘ to pass parameters into this method. /// </summary> public void RecordedMethod1() { #region Variable Declarations WinEdit uIItemEdit = this.UINewtabInternetExplorWindow.UIItemWindow.UIItemEdit; HtmlEdit uIWDEdit = this.UINewtabInternetExplorWindow.UIDocument.UIWDEdit; HtmlInputButton uISearchButton = this.UINewtabInternetExplorWindow.UIDocument.UISearchButton; #endregion // Go to web page ‘about:Tabs‘ using new browser instance this.UINewtabInternetExplorWindow.LaunchUrl(new Uri("http://www.baidu.com")); // Type ‘www.baidu{Enter}‘ in text box //Keyboard.SendKeys(uIItemEdit, this.RecordedMethod1Params.UIItemEditSendKeys, ModifierKeys.None); // Type ‘jaxu cnblogs‘ in ‘wd‘ text box uIWDEdit.Text = this.RecordedMethod1Params.UIWDEditText; // Click ‘百度一下‘ button Mouse.Click(uISearchButton, new Point(61, 18)); } public virtual RecordedMethod1Params RecordedMethod1Params { get { if ((this.mRecordedMethod1Params == null)) { this.mRecordedMethod1Params = new RecordedMethod1Params(); } return this.mRecordedMethod1Params; } } private RecordedMethod1Params mRecordedMethod1Params; } /// <summary> /// Parameters to be passed into ‘RecordedMethod1‘ /// </summary> [GeneratedCode("Coded UITest Builder", "12.0.21005.1")] public class RecordedMethod1Params { #region Fields /// <summary> /// Go to web page ‘about:Tabs‘ using new browser instance /// </summary> public string UINewtabInternetExplorWindowUrl = "about:Tabs"; /// <summary> /// Type ‘www.baidu{Enter}‘ in text box /// </summary> public string UIItemEditSendKeys = "www.baidu{Enter}"; /// <summary> /// Type ‘jaxu cnblogs‘ in ‘wd‘ text box /// </summary> public string UIWDEditText = "jaxu cnblogs"; #endregion } }
大部分代码是由Coded UI Test Builder自动生成的,我们只编写了TestSearchResult()方法,用来寻找控件并获取到其中的值来进行判断。测试结果的判断通过Assert断言来完成,Assert提供了多种方法以帮助我们实现不同的判断,具体的内容可以参考msdn。然后对RecordedMethod1()方法做了适当修改。TestSearchResult()方法中对于如何查找和遍历UI控件在稍后的章节中会讨论到。然后我们将所有代码的调用放到CodedUITest1.cs文件中执行。
[TestMethod] public void CodedUITestMethod1() { UIMap1 uimap = new UIMap1(); uimap.RecordedMethod1(); uimap.TestSearchResult(); }
现在可以通过Test Explorer窗口或者直接使用测试方法的上下文菜单运行或调试该测试方法。如果通过测试,测试方法前面会显示绿色的图标,否则会显示红色的叉。Visual Studio会为每次测试生成对应的测试报告,在工程目录下的TestResults文件夹中可以找到所有的测试报告。
有关Assert断言
在自动化测试中,Assert断言一旦遇到测试失败的情况就会抛出异常,从而导致接下来的测试方法或任务不会继续执行。也就是说,如果一个测试工程中包含了诸多测试方法,经常的情况是一个测试工程中会包含很多个测试类,每个类针对不同的测试用例,而每个测试类中又包含了很多个不同的测试方法。面对如此庞大的一个测试工程,通常会花上数十分钟甚至数小时才能将预定好的所有测试方法跑完,我们当然不希望看到由于某一个测试方法失败而导致剩下的所有测试方法均不能得到执行。在自动化测试中,测试方法测试失败的情况是很普遍的,成功或失败都是一种结果,这总比程序运行到一半抛出异常要好得多。
然而,Assert断言总会在测试失败的时候抛出异常,从而终止程序运行。如下面的测试方法,如果前两个断言中有任何一个失败的话,则剩下的断言不会被执行。
[TestMethod] public void CheckVariousSumResults() { Assert.AreEqual(3, this.Sum(1001, 1, 2)); Assert.AreEqual(3, this.Sum(1, 1001, 2)); Assert.AreEqual(3, this.Sum(1, 2, 1001)); }
一个有效的解决办法是将每一个断言分别放到不同的测试方法中,如下面的代码:
[TestMethod] public void Sum_1001AsFirstParam_Returns3() { Assert.AreEqual(3, this.Sum(1001, 1, 2)); } [TestMethod] public void Sum_1001AsMiddleParam_Returns3() { Assert.AreEqual(3, this.Sum(1, 1001, 2)); } [TestMethod] public void Sum_1001AsThirdParam_Returns3() { Assert.AreEqual(3, this.Sum(1, 2, 1001)); }
然而在大多数情况下这可能行不通。例如你需要测试一个包含100行的table,对每一行的title列进行text测试,在这种情况下你根本无法为每一个断言编写不同的测试方法。首先你无法确定测试方法的数量,其次过多的测试方法会增加维护成本。
另一种我听到过的解决方法是使用参数化测试,然而据我所知,Coded UI Test中好像并不支持。在其它测试环境中或许有更好的解决办法。
或许可以使用try-catch语句来截获Assert断言所抛出的异常,使程序能够继续运行下去。然后我们将所有截获到的异常信息输出到自定义的文件中,即自定义测试报告!测试报告可以是任意类型的文档,记事本或HTML比较常用。既然可以使用try-catch来截获Assert断言的异常欣喜,那么我们会很自然地想到使用下面的方法:
[TestMethod] public void CheckVariousSumResults() { MultiAssert.Aggregate( () => Assert.AreEqual(3, this.Sum(1001, 1, 2)), () => Assert.AreEqual(3, this.Sum(1, 1001, 2)), () => Assert.AreEqual(3, this.Sum(1, 2, 1001))); } public static class MultiAssert { public static void Aggregate(params Action[] actions) { var exceptions = new List<AssertFailedException>(); foreach (var action in actions) { try { action(); } catch (AssertFailedException ex) { exceptions.Add(ex); } } var assertionTexts = exceptions.Select(assertFailedException => assertFailedException.Message); if (0 != assertionTexts.Count()) { throw new AssertFailedException( assertionTexts.Aggregate( (aggregatedMessage, next) => aggregatedMessage + Environment.NewLine + next)); } } }
上面的代码可以很有效地解决问题,但仍会存在问题。MultiAssert.Aggreate()方法中过多的断言最终会将所有的异常信息抛出,这会大大降低异常信息的可读性,不太利于我们从测试测试报告中分析出错的原因。要知道,测试方法最终的目的不是要让测试程序运行通过,而是通过测试报告来分析被测试对象可能具有的问题。
下面是一个例子,可以用来有效地解决上面提出的问题。
public static class AssertWrapper { public static string AreEqual<T>(T expected, T actual, string message) { string result = null; try { Assert.AreEqual(expected, actual, message); TestLog.WritePass(message); } catch (AssertFailedException ex) { result = ex.Message; TestLog.WriteError(message); } catch (Exception ex) { result = ex.Message; TestLog.WriteError(message); } return result; } public static string AreEqual(string expected, string actual, string message) { string result = null; try { Assert.AreEqual(expected, actual, message); TestLog.WritePass(message); } catch (AssertFailedException ex) { result = ex.Message; TestLog.WriteError(result); } catch (Exception ex) { result = ex.Message; TestLog.WriteError(result); } return result; } public static string AreEqual(string expected, string actual, bool ignorecase, string message) { string result = null; try { Assert.AreEqual(expected, actual, ignorecase, message); TestLog.WritePass(message); } catch (AssertFailedException ex) { result = ex.Message; TestLog.WriteError(result); } catch (Exception ex) { result = ex.Message; TestLog.WriteError(result); } return result; } public static string Fail(string message) { string result = null; try { Assert.Fail(message); } catch (AssertFailedException ex) { result = ex.Message; TestLog.WriteError(result); } return result; } }
AssertWrapper类中的方法可以有多个重载,以满足不同的需要,其基本思想就是使用try-catch语句来截获Assert断言所抛出的异常。TestLog类中的方法负责写测试报告,你可以将测试报告定义成任何形式。然后定义一个TestSettings类用来收集测试工程中所有的测试断言。
public class TestSettings { public static void AddResult(List<string> resultList, string result) { if (result != null) { if (resultList == null) { resultList = new List<string>(); } resultList.Add(result); } } }
在每一个.uitest文件的类中,这样使用上面的方法:
public List<string> faillist; public void ValidateHeader() { TestSettings.AddResult(faillist,AssertWrapper.AreEqual(true, uIHeader.Exists, "test page: Validate Page header text")); }
然后,在所有的测试方法中添加下面的代码(faillist为泛型List对象,被定义为TestMethod所在的类的私有变量,同时我们通过faillist.AddRange(testPage.faillist)语句将测试页面类中的泛型List内容添加过来):
if (faillist != null && faillist.Count > 0) { StringBuilder fail = new StringBuilder(); foreach (string s in faillist) { fail.AppendLine(s); } Assert.Fail(fail.ToString()); }
这样,可以对该测试方法中包含的所有Assert断言进行统一管理。这样做有几个好处:
Coded UI Test如何搜索一个控件?
在Coded UI Test中,最常见的问题是如何找到被测试的控件。只有找到被测试的对象,才能使用断言来判断其中的属性是否满足预期的值。大多数情况下,我们都会使用Coded UI Test Builder窗口来捕获UI上的控件,但有些情况下我们不得不自行搜索需要的控件。一个简单的例子,在列表控件中如何查找第一个子元素中所包含的文本。就像本文一开始给出的测试需求。如果你通过Coded UI Test Builder直接查找第一个子元素,其中生成的搜索条件往往具有特定性,当页面的条件发生变化,特定的搜索条件不一定能找到对应的控件。
查看.Designer.cs文件中自动生成的代码,所有控件的定义都会包含类似于下面代码的搜索条件:
HtmlEdit mUIEmailEdit = new HtmlEdit(someAncestorControl); mUIEmailEdit.SearchProperties[HtmlEdit.PropertyNames.Id] = "email"; mUIEmailEdit.SearchProperties[HtmlEdit.PropertyNames.Name] = "email"; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.LabeledBy] = null; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.Type] = "SINGLELINE"; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.Title] = null; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.Class] = null; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.ControlDefinition] = "id=email size=25 name=email"; mUIEmailEdit.FilterProperties[HtmlEdit.PropertyNames.TagInstance] = "7"; mUIEmailEdit.Find();
Coded UI Test会试图通过所有已知的条件来搜索指定的控件,它使用广度优先查找方法(Breadth-First)。所有SearchProperties可以被视为使用AND条件进行查找,如果通过SearhProperties已经找到对应的控件,则所有的FilterProperties条件不会被使用。如果通过所有的SearchProperties条件未能找到对应的控件,则尝试逐个使用给出的FilterProperties条件进行搜索,直到找到匹配的控件。如果通过以上给出的所有条件最终找到多余一个的匹配项,则第一个匹配的元素即为找到的控件。
在上面给出的例子中,会按照如下顺利进行搜索:
下面的流程图说明了这一过程:
有一点需要注意:
在Web controls中,搜索条件的使用可能会涉及到浏览器兼容性问题。如筛选条件最终需要通过InnerText来确定控件,而该属性在某些浏览器上并不支持,此时可能引发异常。在程序编码过程中尝试给特定的控件指定ID属性可以更好的解决这一问题,这就需要与程序开发人员进行有效的沟通。从这一点也可以看出,测试驱动开发的重要性。
不要尝试通过GetChildren()方法来遍历所有的控件,因为该方法返回结果会很慢,尤其是当页面中存在大量控件时。可以使用临近的祖先节点对该控件进行定义(构造函数的参数可以用来指定被搜索控件的祖先),然后通过给定SearchProperties或FilterProperties来对控件进行筛选,然后使用FindMatchingControls()方法来确定要搜索的控件。如下面的代码用来遍历Table元素从而找到表中所有的<th/>和<td/>:
HtmlTable uITable = this.UIRelWindow.UIRelDocument.UITable; HtmlRow rowall = new HtmlRow(uITable); UITestControlCollection rows = rowall.FindMatchingControls(); int rowCount = rows.Count; for (int i = 0; i < rowCount; i++) { HtmlHeaderCell allTH = new HtmlHeaderCell(rows[i]); HtmlCell allTD = new HtmlCell(rows[i]); UITestControlCollection THs = allTH.FindMatchingControls(); UITestControlCollection TDs = allTD.FindMatchingControls(); ... ... }
代码结构调整
.uitest文件针对的是每一个测试页面,每个页面都有单独的验证方法用来测试页面上各个不同的部分,具有良好结构的代码可以使整个测试工程看起来思路清晰。如果有必要,你完全可以使用设计模式来更加简练地组织工程中的测试方法和类。一个完好的测试工程代码结构看起来像这样:
public class TestRunner { public TestRunner() { homePage = new UI.HomePageClasses.HomePage(); } #region Home page actions and validate method private UI.HomePageClasses.HomePage homePage; public UI.PageClasses.HomePage HomePage { get { if ((this.homePage == null)) { this.homePage = new UI.PageClasses.HomePage(); } return this.homePage; } set { homePage = value; } } public void LaunchHomePage() { HomePage.LaunchHomePage(new System.Uri(TestSettings.GetCurrentSiteURL())); } public void ValidateHomePageText() { HomePage.ValidateHomePageText(); } }
使用TestRunner类将工程中所有的验证方法和UI Actions方法进行包装,然后在测试方法中进行调用。
[TestMethod] public void IncomeStatementsTest() { testrunner.NavigateToTestPage(); testrunner.ValidateSomething(); } [TestInitialize()] public void MyTestInitialize() { testrunner = new TestRunner(); testrunner.LaunchHomePage(); } ////Use TestCleanup to run code after each test has run [TestCleanup()] public void MyTestCleanup() { testrunner = null; } private TestRunner testrunner;
忘记说明一点,带有[CodedUITest]特征属性的类中,我们可以借用MyTestInitialize()方法和MyTestCleanup()方法进行一些初始化操作和清理工作。不要在该类的构造函数中添加任何代码,通过带有[TestInitialize]特征属性的方法进行初始化工作。同样,带有[TestCleanup]特征属性的方法可以用来进行一些清理工作。
另外,和大多数工程一样,Coded UI Test工程允许使用App.config文件。在工程中添加该文件并加入<appSettings></appSettings>节点以设置配置信息。
<configuration> <appSettings> <add key ="" value=""/> </appSettings> </configuration>
如何使用命令行方式运行测试方法?
除了在Visual Studio中运行测试方法外,我们还可以通过其它许多方式来运行测试方法。使用测试代理和测试控制器可以对所有的测试方法进行有效管理,并可以将测试方法分发到不同的测试机上单独进行测试,但需要在服务器上进行部署,MSDN上有相应的介绍,这里主要介绍如何通过命令行方式来运行测试方法。
MSTest /testcontainer:CodedUITestProject2.dll /test:CodedUITest1.CodedUITestMethod1
msdn上有对MSTest.exe命令行所有参数的说明。有几点需要说明一下:
如果你想分发你的测试工程在其它机器上运行,可以编写.bat文件并将Coded UI Test工程生成的.dll文件放到同一文件夹下。.bat文件的内容看起来像下面这样:
@echo off @set PATH=c:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE;%PATH% echo ****** This program will start a Coded UI Test Method ****** pause MSTest /testcontainer:CodedUITest1.dll /test:CodedUITest1.CodedUITestMethod1 echo ****** End Coded UI Test Method ******** pause
如何使用Coded UI Test对Webpage进行自动化测试,布布扣,bubuko.com
如何使用Coded UI Test对Webpage进行自动化测试
标签:des winform style blog class code
原文地址:http://www.cnblogs.com/jaxu/p/3706652.html