51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1899|回复: 4
打印 上一主题 下一主题

如何使用Coded UI Test对Webpage进行自动化测试

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-4-13 15:36:27 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
在Visual Studio中,Coded UI Test已经不是什么新特性了,较早版本的Visual Studio中就已经有这个东东了。
它主要用来帮助自动化测试工程师和开发人员确保程序在UI方面没有任何问题。这其中包含了丰富的内容。
在这之前,我一直对自动化测试的工作以及什么是自动化测试一知半解,具备自动化测试编码能力的工程师
所掌握的技能在某种程度上要远超程序开发人员和设计人员,对于这一点,我早有耳闻!但直到亲身体验我
才确信,测试工作远没有我们想象得那么简单。开发人员或许花上数小时就可以完成项目中某一个独立模块
并使其在一定范围内正常运行,然而,自动化测试工程师也许会花上好几天的时间来编写对应的自动化测
试代码来确保这一功能运行正常。

  Coded UI Test包含了十分丰富的API库,它可以录制和回放UI操作,捕捉UI元素并获取属性的值,并生
成操作代码。测试人员在生成代码的基础上对测试对象的值进行逻辑判断并给出测试结果。创建一个Code
d UI Test很容易,大多数情况下,我们只需要借助于Visual Studio就可以完成绝大部分操作

基本操作

  (本文演示的所有代码和操作均在Visual Sutdio 2013和Windows 8.1 + IE 11环境下)

  在Visual Studio中开始创建一个Coded UI Test Project。这很简单!



  工程创建成功后,Visual Studio会问你是马上开始一个新的UI录制还是选择已经录制好的操作。当然你也
可以选择取消,在后面的步骤里再开始UI录制。



  工程默认生成CodedUITest1.cs文件。在开始录制UI操作之前,对基本概念做一下介绍:

Coded UI Test工程的运行是从包含有[CodedUITest]特征属性的类开始的。一个工程中可以有多个这样的类。
与普通的工程不同,我们不能通过F5或者点击Visual Studio中的运行按钮来调试或直接运行工程,Coded UI
Test工程必须通过Test Explorer或者在包含有[CodedUITest]特征属性的类中来选择运行相应的测试方法。
在带有[CodedUITest]特征属性的类中,所有的测试方法都必须带有[TestMethod]特征属性,以表示它是一个
有效的测试方法,可以直接运行。
通过选择TEST->Windows->Test Explorer可以打开Test Explorer窗口,在Test Explorer窗口中可以查看工程
中所有的测试方法并选择运行。当然,你也可以在带有[TestMethod]特征属性的测试方法的代码块中右键选
择运行该测试方法。
测试方法同样可以调试。在选择运行测试方法时,你会看到有Debug Test的菜单,调试的过程和在普通工程
中一样。


  然后,我们开始一个UI录制。在工程中添加一个Coded UI Test Map文件。创建成功后Visual Studio会自
动在屏幕的右下角打开Coded UI Test Builder窗口,以方便我们进行UI录制操作。



  借用MSDN上的图片来对Coded UI Test Builder窗口上按钮的功能做一下简单的说明:

Record icon - 开始一个UI操作的录制。例如菜单导航、按钮点击等操作。
Edit steps - 对录制的步骤进行编辑,调整或者删除冗余的步骤。
Add assertions - 不仅仅是添加断言,通过点击该按钮并拖放到测试对象的UI上以选取控件,然后你可以添
加断言。
Generate code - 这一步很关键,在完成以上所有的操作后,通过点击该按钮Visual Studio会自动为你生成代
码。生成的代码在.uitest文件下面的.Desinger.cs文件中可以看到。注意不要手动修改自动生成的代码,这会
导致下次通过Coded UI Test Builder窗口对.uitest文件进行修改时某些对象或操作不同步。
Close to finish recoding - 在关闭Coded UI Test Builder窗口之前,确保所做的修改已经生成了对应的代码。
如果要修改.Designer.cs文件中自动生成的代码,可以在Solution Explorer中右键选择.uitest文件,然后选择E
dit With Coded UI Test Builder。在后面的步骤中我们会讲到这一点。


  UI Action的录制和UI控件的选择操作是分开的。让我们先开始UI Action的录制。

点击Coded UI Test Builder窗口中的红色按钮
打开IE浏览器,在地址栏中输入http://www.baidu.com以导航到百度搜索引擎
输入关键字“jaxu cnblogs”,点击“百度一下”
在Coded UI Test Builder窗口中点击Pause,然后点击Edit steps,删除不必要的步骤。我删除了操作中的第4
步和第5步。
选择Generate code,对所要生成的方法取个名字,然后点击Add and Generate按钮。Visual Studio为我们所
录制的UI操作步骤生成了对应的代码。
关闭Coded UI Test Builder窗口,在Visual Studio中查看刚才生成的代码。





  在Solution Explorer中展开UIMap1.uitest文件,选择并打开UIMap1.Designer.cs文件,可以看到刚才所生
成的代码。是不是很想现在就运行一下,来看看这些自动生成的代码如何运行?现在还不行,因为单纯的UI
Action运行没有任何意义,Coded UI Test的真正意义是通过UI操作来定位到UI上的某一个特定元素,并最终
通过断言来确定该元素的属性是否和预期的值相等。

  为了能够手动修改.Designder.cs文件中生成的代码,我们需要将它们移到.cs文件中。在Solution Explor
er中双击UIMap1.uitest文件,在打开的窗口中我们可以看到左边是UI Actions所生成的步骤,右边是UI Contr
ol Map(稍后我们会用到它)。在左边的UI Actions中选择根节点RecordedMethod1,然后在顶部的菜单中
选择Move code to UIMap1.cs,代码会被移到.cs文件以方便我们进行修改。完成该步骤之后,我们可以在.
cs文件中看到这些代码并做相应的修改。



  你可能已经注意到了,自动生成的代码中有些对象的名字看起来并不那么好,甚至有些还包含了中文。
你希望修改它们,但是不要在.Designer.cs文件中做任何修改!还记得前面我们讲过的Edit With Coded UI T
est操作吗?在Solution Explorer中右键选择UIMap1.uitest文件,右键选择Edit With Coded UI Test打开Code
d UI Test Builder窗口,然后点击Add assertions按钮(就是那个用来选择UI Control的按钮),然后展开UI C
ontrol Map界面。如下图,我们可以对其中生成的UI Controls进行编辑和重命名。



  完成修改之后再次点击Generate code按钮并关闭Coded UI Test Builder窗口,此时.Designer.cs文件中自
动生成的代码已经做了修改。由于前面我们已经将相关的UI Actions部分的代码移到.cs文件里了,所以重命
名的对象我们还需要在.cs文件中手动进行修改,否则编译时会出错。建议在将代码移到.cs文件之前完成自
动生成代码的修改工作,以避免手动修改过多的代码。

  然后我们需要捕捉到百度搜索结果的UI控件,并对其中的结果进行判断。仍然使用Coded UI Test Buil
der窗口。

打开浏览器并导航到百度,输入关键字“jaxu cnblogs”并点击“百度一下”
在Coded UI Test Builder窗口中点击Add assertions按钮并拖放到浏览器窗口,同时指向你想要捕捉的控件上
。有些控件很难精确捕捉到,没有关系,你可以先选中临近的控件,然后使用Coded UI Test Builder窗口中
的方向按钮移动到你所要定位的元素。如下图,我们选择并定位到了百度搜索结果的DIV元素,并将该控件
命名为UIContent_leftPane。
在Coded UI Test Builder窗口的左上角点击添加选择的控件,这一点很重要!忘记这一步则所选择的控件不
会被添加到生成的代码中。
生成代码并关闭Coded UI Test Builder窗口。


  至此,所有的UI Actions和UI Controls都已经定义完毕,接下来我们要编码以完成对搜索结果的判断。
借助于自动生成的代码,我们编写了下面的测试方法以实现文章最开始的测试需求。

复制代码
  1. namespace CodedUITestProject2.UIMap1Classes
  2. {
  3.     using Microsoft.VisualStudio.TestTools.UITesting.HtmlControls;
  4.     using Microsoft.VisualStudio.TestTools.UITesting.WinControls;
  5.     using System;
  6.     using System.Collections.Generic;
  7.     using System.CodeDom.Compiler;
  8.     using Microsoft.VisualStudio.TestTools.UITest.Extension;
  9.     using Microsoft.VisualStudio.TestTools.UITesting;
  10.     using Microsoft.VisualStudio.TestTools.UnitTesting;
  11.     using Keyboard = Microsoft.VisualStudio.TestTools.UITesting.Keyboard;
  12.     using Mouse = Microsoft.VisualStudio.TestTools.UITesting.Mouse;
  13.     using MouseButtons = System.Windows.Forms.MouseButtons;
  14.     using System.Drawing;
  15.     using System.Windows.Input;
  16.     using System.Text.RegularExpressions;


  17.     public partial class UIMap1
  18.     {
  19.         public void TestSearchResult()
  20.         {
  21.             HtmlDiv resultPanel = this.UINewtabInternetExplorWindow.UIJaxucnblogs_SearchDocument.UIContent_leftPane;
  22.             HtmlDiv resultPanelFirst = (HtmlDiv)resultPanel.GetChildren()[0];
  23.             HtmlHyperlink link = new HtmlHyperlink(resultPanelFirst);
  24.             Assert.AreEqual("Jaxu - 博客园", link.InnerText, "Validation is failed.");
  25.         }

  26.         /// <summary>
  27.         /// RecordedMethod1 - Use 'RecordedMethod1Params' to pass parameters into this method.
  28.         /// </summary>
  29.         public void RecordedMethod1()
  30.         {
  31.             #region Variable Declarations
  32.             WinEdit uIItemEdit = this.UINewtabInternetExplorWindow.UIItemWindow.UIItemEdit;
  33.             HtmlEdit uIWDEdit = this.UINewtabInternetExplorWindow.UIDocument.UIWDEdit;
  34.             HtmlInputButton uISearchButton = this.UINewtabInternetExplorWindow.UIDocument.UISearchButton;
  35.             #endregion

  36.             // Go to web page 'about:Tabs' using new browser instance
  37.             this.UINewtabInternetExplorWindow.LaunchUrl(new Uri("http://www.baidu.com"));

  38.             // Type 'www.baidu{Enter}' in text box
  39.             //Keyboard.SendKeys(uIItemEdit, this.RecordedMethod1Params.UIItemEditSendKeys, ModifierKeys.None);

  40.             // Type 'jaxu cnblogs' in 'wd' text box
  41.             uIWDEdit.Text = this.RecordedMethod1Params.UIWDEditText;

  42.             // Click '百度一下' button
  43.             Mouse.Click(uISearchButton, new Point(61, 18));
  44.         }

  45.         public virtual RecordedMethod1Params RecordedMethod1Params
  46.         {
  47.             get
  48.             {
  49.                 if ((this.mRecordedMethod1Params == null))
  50.                 {
  51.                     this.mRecordedMethod1Params = new RecordedMethod1Params();
  52.                 }
  53.                 return this.mRecordedMethod1Params;
  54.             }
  55.         }

  56.         private RecordedMethod1Params mRecordedMethod1Params;
  57.     }
  58.     /// <summary>
  59.     /// Parameters to be passed into 'RecordedMethod1'
  60.     /// </summary>
  61.     [GeneratedCode("Coded UITest Builder", "12.0.21005.1")]
  62.     public class RecordedMethod1Params
  63.     {

  64.         #region Fields
  65.         /// <summary>
  66.         /// Go to web page 'about:Tabs' using new browser instance
  67.         /// </summary>
  68.         public string UINewtabInternetExplorWindowUrl = "about:Tabs";

  69.         /// <summary>
  70.         /// Type 'www.baidu{Enter}' in text box
  71.         /// </summary>
  72.         public string UIItemEditSendKeys = "www.baidu{Enter}";

  73.         /// <summary>
  74.         /// Type 'jaxu cnblogs' in 'wd' text box
  75.         /// </summary>
  76.         public string UIWDEditText = "jaxu cnblogs";
  77.         #endregion
  78.     }
  79. }
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

3#
 楼主| 发表于 2018-4-13 15:37:09 | 只看该作者
复制代码
  在每一个.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断言进行统一管理。这样做有几个好处:

保证程序能够正常运行
对每一个测试方法而言,其中会包含多个验证方法,而每一个验证方法中又会包含有多个Assert断言,使
用上述方法可以有效管理所有的断言。
自定义测试报告的格式
可以定义测试方法是通过或者失败。失败的测试方法通过Assert.Fail()方法来抛出异常,如果一个测试方法
中没有异常抛出,则会被默认为测试成功,尽管事实上测试是失败的。


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条件进行按序匹配(Ordered match),直到找到匹配的控件
。如果通过以上给出的所有条件最终找到多余一个的匹配项,则第一个匹配的元素即为找到的控件。

  在上面给出的例子中,会按照如下顺利进行搜索:

搜索会从someAncestorControl控件开始,因为它被作为要搜索控件的祖先。注意祖先并不一定是父节点,
它可以是被搜索控件的任意上级控件。
每一次遍历搜索时,会使用所有已定义的SearchProperties条件进行筛选,假设有三个控件均满足搜索条件
,如X1,X2和X3,此时会继续使用剩下的FilterProperties条件进行筛选。
对控件X1,X2和X3分别使用已定义的FilterProperties条件进行筛选,直到匹配到唯一的控件,或者所有的
FilterProperties条件均比较完毕。
  下面的流程图说明了这一过程:



  有一点需要注意:

对Web controls进行搜索:SearchProperties和FilterProperties均支持
对WinForms和WPF controls进行搜索:仅SearchProperties支持
  在Web controls中,搜索条件的使用可能会涉及到浏览器兼容性问题。如筛选条件最终需要通过InnerT
ext来确定控件,而该属性在某些浏览器上并不支持,此时可能引发异常。在程序编码过程中尝试给特定的
控件指定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()方法和MyTestClea
nup()方法进行一些初始化操作和清理工作。不要在该类的构造函数中添加任何代码,通过带有[TestInitializ
e]特征属性的方法进行初始化工作。同样,带有[TestCleanup]特征属性的方法可以用来进行一些清理工作。

  另外,和大多数工程一样,Coded UI Test工程允许使用App.config文件。在工程中添加该文件并加入
<appSettings></appSettings>节点以设置配置信息。

<configuration>
<appSettings>
<add key ="" value=""/>
</appSettings>
</configuration>


如何使用命令行方式运行测试方法?

  除了在Visual Studio中运行测试方法外,我们还可以通过其它许多方式来运行测试方法。使用测试代理和
测试控制器可以对所有的测试方法进行有效管理,并可以将测试方法分发到不同的测试机上单独进行测试,
但需要在服务器上进行部署,MSDN上有相应的介绍,这里主要介绍如何通过命令行方式来运行测试方法。

编译Coded UI Test工程,将生成的.dll文件复制到指定的目录下。
打开Windows命令行窗口,转到目录c:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE
或者直接打开Developer Command Prompt for VS2012
使用如下命令行执行测试方法
MSTest /testcontainer:CodedUITestProject2.dll /test:CodedUITest1.CodedUITestMethod1
  msdn上有对MSTest.exe命令行所有参数的说明。有几点需要说明一下:

/testcontainer中有必要指定.dll文件的路径名称
/test用来指定.dll文件中所包含的测试方法的全名称。注意这里的全名称包含了测试方法所在的类名以及方法
名,类必须包含[CodedUITest]特征属性,测试方法必须包含[TestMethod]特征属性。
  如果你想分发你的测试工程在其它机器上运行,可以编写.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
回复 支持 反对

使用道具 举报

该用户从未签到

2#
 楼主| 发表于 2018-4-13 15:36:59 | 只看该作者


复制代码
  大部分代码是由Coded UI Test Builder自动生成的,我们只编写了TestSearchResult()方法,用来寻找控件
并获取到其中的值来进行判断。测试结果的判断通过Assert断言来完成,Assert提供了多种方法以帮助我们实
现不同的判断,具体的内容可以参考msdn。然后对RecordedMethod1()方法做了适当修改。TestSearchResu
lt()方法中对于如何查找和遍历UI控件在稍后的章节中会讨论到。然后我们将所有代码的调用放到CodedUITes
t1.cs文件中执行。

复制代码
  1. [TestMethod]
  2. public void CodedUITestMethod1()
  3. {
  4. UIMap1 uimap = new UIMap1();
  5. uimap.RecordedMethod1();

  6. uimap.TestSearchResult();
  7. }
复制代码

复制代码
  现在可以通过Test Explorer窗口或者直接使用测试方法的上下文菜单运行或调试该测试方法。如果通过测
试,测试方法前面会显示绿色的图标,否则会显示红色的叉。Visual Studio会为每次测试生成对应的测试报告
,在工程目录下的TestResults文件夹中可以找到所有的测试报告。



有关Assert断言

  在自动化测试中,Assert断言一旦遇到测试失败的情况就会抛出异常,从而导致接下来的测试方法或任
务不会继续执行。也就是说,如果一个测试工程中包含了诸多测试方法,经常的情况是一个测试工程中会包
含很多个测试类,每个类针对不同的测试用例,而每个测试类中又包含了很多个不同的测试方法。面对如此
庞大的一个测试工程,通常会花上数十分钟甚至数小时才能将预定好的所有测试方法跑完,我们当然不希望
看到由于某一个测试方法失败而导致剩下的所有测试方法均不能得到执行。在自动化测试中,测试方法测
试失败的情况是很普遍的,成功或失败都是一种结果,这总比程序运行到一半抛出异常要好得多。

  然而,Assert断言总会在测试失败的时候抛出异常,从而终止程序运行。如下面的测试方法,如果前两
个断言中有任何一个失败的话,则剩下的断言不会被执行。

复制代码
  1. [TestMethod]
  2. public void CheckVariousSumResults()
  3. {
  4. Assert.AreEqual(3, this.Sum(1001, 1, 2));
  5. Assert.AreEqual(3, this.Sum(1, 1001, 2));
  6. Assert.AreEqual(3, this.Sum(1, 2, 1001));
  7. }
复制代码

复制代码
  一个有效的解决办法是将每一个断言分别放到不同的测试方法中,如下面的代码:

复制代码
  1. [TestMethod]
  2. public void Sum_1001AsFirstParam_Returns3()
  3. {
  4. Assert.AreEqual(3, this.Sum(1001, 1, 2));
  5. }
  6. [TestMethod]
  7. public void Sum_1001AsMiddleParam_Returns3()
  8. {
  9. Assert.AreEqual(3, this.Sum(1, 1001, 2));
  10. }
  11. [TestMethod]
  12. public void Sum_1001AsThirdParam_Returns3()
  13. {
  14. Assert.AreEqual(3, this.Sum(1, 2, 1001));
  15. }
复制代码

复制代码
  然而在大多数情况下这可能行不通。例如你需要测试一个包含100行的table,对每一行的title列进行text测试,在这种情况下你根本无法为每一个断言编写不同的测试方法。首先你无法确定测试方法的数量,其次过多的测试方法会增加维护成本。

  另一种我听到过的解决方法是使用参数化测试,然而据我所知,Coded UI Test中好像并不支持。在其它测试环境中或许有更好的解决办法。

  或许可以使用try-catch语句来截获Assert断言所抛出的异常,使程序能够继续运行下去。然后我们将所有截获到的异常信息输出到自定义的文件中,即自定义测试报告!测试报告可以是任意类型的文档,记事本或HTML比较常用。既然可以使用try-catch来截获Assert断言的异常欣喜,那么我们会很自然地想到使用下面的方法:

复制代码
  1. [TestMethod]
  2. public void CheckVariousSumResults()
  3. {
  4. MultiAssert.Aggregate(
  5. () => Assert.AreEqual(3, this.Sum(1001, 1, 2)),
  6. () => Assert.AreEqual(3, this.Sum(1, 1001, 2)),
  7. () => Assert.AreEqual(3, this.Sum(1, 2, 1001)));
  8. }

  9. public static class MultiAssert
  10. {
  11. public static void Aggregate(params Action[] actions)
  12. {
  13. var exceptions = new List<AssertFailedException>();

  14. foreach (var action in actions)
  15. {
  16. try
  17. {
  18. action();
  19. }
  20. catch (AssertFailedException ex)
  21. {
  22. exceptions.Add(ex);
  23. }
  24. }

  25. var assertionTexts =
  26. exceptions.Select(assertFailedException => assertFailedException.Message);
  27. if (0 != assertionTexts.Count())
  28. {
  29. throw new
  30. AssertFailedException(
  31. assertionTexts.Aggregate(
  32. (aggregatedMessage, next) => aggregatedMessage + Environment.NewLine + next));
  33. }
  34. }
  35. }
复制代码

复制代码
  上面的代码可以很有效地解决问题,但仍会存在问题。MultiAssert.Aggreate()方法中过多的断言最终会
将所有的异常信息抛出,这会大大降低异常信息的可读性,不太利于我们从测试测试报告中分析出错的原因。
要知道,测试方法最终的目的不是要让测试程序运行通过,而是通过测试报告来分析被测试对象可能具有的
问题。

  下面是一个例子,可以用来有效地解决上面提出的问题。

复制代码
  1. public static class AssertWrapper
  2. {
  3. public static string AreEqual<T>(T expected, T actual, string message)
  4. {
  5. string result = null;
  6. try
  7. {
  8. Assert.AreEqual(expected, actual, message);
  9. TestLog.WritePass(message);
  10. }
  11. catch (AssertFailedException ex)
  12. {
  13. result = ex.Message;
  14. TestLog.WriteError(message);
  15. }
  16. catch (Exception ex)
  17. {
  18. result = ex.Message;
  19. TestLog.WriteError(message);
  20. }
  21. return result;
  22. }

  23. public static string AreEqual(string expected, string actual, string message)
  24. {
  25. string result = null;
  26. try
  27. {
  28. Assert.AreEqual(expected, actual, message);
  29. TestLog.WritePass(message);

  30. }
  31. catch (AssertFailedException ex)
  32. {
  33. result = ex.Message;
  34. TestLog.WriteError(result);
  35. }
  36. catch (Exception ex)
  37. {
  38. result = ex.Message;
  39. TestLog.WriteError(result);
  40. }
  41. return result;
  42. }

  43. public static string AreEqual(string expected, string actual, bool ignorecase, string message)
  44. {
  45. string result = null;
  46. try
  47. {
  48. Assert.AreEqual(expected, actual, ignorecase, message);
  49. TestLog.WritePass(message);

  50. }
  51. catch (AssertFailedException ex)
  52. {
  53. result = ex.Message;
  54. TestLog.WriteError(result);
  55. }
  56. catch (Exception ex)
  57. {
  58. result = ex.Message;
  59. TestLog.WriteError(result);
  60. }
  61. return result;
  62. }

  63. public static string Fail(string message)
  64. {
  65. string result = null;
  66. try
  67. {
  68. Assert.Fail(message);
  69. }
  70. catch (AssertFailedException ex)
  71. {
  72. result = ex.Message;
  73. TestLog.WriteError(result);
  74. }
  75. return result;
  76. }
  77. }
复制代码

复制代码
  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);
        }
    }
}


回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-18 00:26 , Processed in 0.073689 second(s), 24 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表