VS2010 测试功能之旅:编码的UI测试(4)-通“.NET研究”过编写测试代码的方式建立UI测试(上)

简介:   回顾  在之前的入门篇系列中,分别介绍了一个简单的示例, 操作动作的录制原理,通过修改UIMap.UItest文件控制操作动作代码的生成,对象的识别原理。接下来正式进入我们UI测试的进阶篇,在这一章,将讲述如何初步通过自己编写代码的方式来建立UI测试。

  回顾

  在之前的入门篇系列中,分别介绍了一个简单的示例, 操作动作的录制原理通过修改UIMap.UItest文件控制操作动作代码的生成对象的识别原理。接下来正式进入我们UI测试的进阶篇,在这一章,将讲述如何初步通过自己编写代码的方式来建立UI测试。

  示例程序

  一个系统的基本功能是增,删,改,查,其中增和改界面基本一样,删就几乎是一个按钮的事,所以我做了一个程序示例(下载点我),拥有增和查两个功能,之后的操作都将会在这个示例之上进行:

  系统主窗口:

  该系统拥有两个功能,“添加用户”和“查询用户”,点击添加用户后,进入添加用户子窗体:

  这里添加用户的时候根据情况会出现以下几个提示框:

  “用户名不能为空”

  “已有重名用户”

  “备注不能为空”

  “添加成功!”

  如果在之前的主窗口,点击查询用户,则进入查询用户子窗体。

  注:系统默认自带了5个用户TestUser1, TestUser2, TestUser3, TestUser4, TestUser5。

  这个窗体不会弹任何提示框,默认进入窗体时,DataGridView里面没有加载数据,现在进行一个说明:

  查询条件-用户名:表示是否按用户名查询(非模糊查询),如果不输入,默认为不按其查询。

  查询条件-用户类型:有三个选项“所有”,“管理员”,“一般用户”。

  查询条件-日期:表示是否按日期查询,如果勾上了日期CheckBox,则旁边的DateTimePicker会启用,然后选择一个具体的日期。

  按钮-查询:就会按以上条件查询。

  按钮-重置:用户名清空,用户类型变成所有,日期取消勾选。

  文本框-用户备注:当查询出数据以后,每选择DataGridView里面中的一行数据,用户备注TextBox会自动加载当前行的用户备注。

  因为篇幅的关系,这里仍然分为上下两部分,上部分介绍添加用户窗体,下部分介绍查询用户窗体和测试之间的衔接。

  如何设计测试

  首先从前面的分析中,就可以得出添加用户实际上是检测是否有那些反例的弹出框弹出,然后正确添加用户,这里设计了一些检查点。

步骤序号

操作步骤

检查点

1

运行主程序exe

检测系统主窗口是否弹出

2

点击添加用户

检测添加用户子窗口是否弹出

3

输入用户名为空,用户类型选择“一般用户”,备注为空

检测是否弹出“用户名不能为空”

4

输入用户名为“TestUser1”(系统默认就已有该用户),用户类型选择“一般用户”,备注为空

检测是否弹出“已有重名用户”

5

输入用户名为“TestUser6”,用户类型选择“一般用户”,备注为空

检测是否弹出“备注不能为空”

6

输入用户名为“TestUser6”,用户类型选择“一般用户”,备注为“Test”

检测是否弹出“添加成功!”

7

点击取消按钮,并退出主窗体

检测是否退出添加用户子窗体和主窗体

  接下来要做的工作就很轻松,我们要将以上的检查点转换为代码。

  对测试进行编码

  实际上,很多自动化测试项目在编写的时候都是采用边录制边编写的方法来进行的,比如复杂的操作可以先录制下来,然后手工去改某些步骤,这里我们将采用这种方法。

  我们需要新建一个项目,然后在添加一个编码的UI测试映射,命名为AddUserUIMap.uitest,建立之后,录制生成器会自动弹出,这个时候,我们什么也不做,直接点击“生成代码”,这样VS2010就会自动生成AddUserUIMap.cs文件和AddUserUIMap.Designer.cs文件,在第二章(下)已经提到,自定义代码可以编写到.cs文件下,因为这里不会被覆盖。

  实现步骤1

  为了实现第一步检查点,首先我们需要捕获主窗体对象,首先我们需要打开示例程序,然后点击录制生成器的准星。

  从点击准星的那一刻起,按住鼠标不放,将鼠标挪动到主窗体直到主窗体被蓝色框选中,这个时候便可以松开鼠标。

  之后我们可以看到对象库中识别了该对象,现在点击对象库上面的“添加”图标,就可以将这个对象正式加入对象库:

  然后选择录制生成器的生成代码。

  之后对象识别代码就生成在了AddUserUIMap.Designer.cs。

  之后我们就可以进入AddUserUIMap.cs(注,这里是.cs,不是.Designer.cs),实现我们第一个步骤的代码Step1_LoginSystem()。

 
   
public void Step1_LoginSystem()
{
// 操作步骤:假设程序在D盘,这句的作用是加载程序
ApplicationUnderTest.Launch( @" D:\TestDemo.exe " );

// 检查点:this.UI系统主窗口Window.WaitForControlExist(6000)的作用为,最多花6秒的时间等待UI系统主窗口Window出现,如果没有出现,返回false,如果出现了,则返回true
Assert.IsTrue( this .UI系统主窗口Window.WaitForControlExist( 6000 ), " 运行主程序exe,检测系统主窗口弹出失败 " );
}

  实现步骤2

  第一步就完成了,接下来实现第二步Step2_ClickAddUser(),因为这里大家可能觉得录制生成的方式更方便,这里我们可以采用录制的方式先生成代码,之后再将.Designer.cs生成的代码迁移到.cs文件下。

  首先打开录制生成器的录制。

  然后在菜单中选择“添加用户”。

  然后停止录制,点击生成代码,生成Step2_ClickAddUser()。

  生成完毕,我们可以看到在.Designer.cs下生成了如下代码。

 
   
public void Step2_ClickAddUser()
{
#region Variable Declarations
WinMenuItem uI添加用户MenuItem
= this .UI系统主窗口Window.UIMenuStrip1MenuBar.UI用户管理MenuItem.UI添加用户MenuItem;
#endregion

// Click '用户管理' -> '添加用户' menu item
Mouse.Click(uI添加用户MenuItem, new Point( 41 , 7 ));
}

  这里我们把这些代码从.Designer.cs剪切到.cs文件下,然后删除AddUserUIMap.uitest文件下<ExecuteActions>节点中的所有内容,因为根据操作动作的录制原理可以了解到,.Designer.cs会根据.uitest文件生成代码,所以需要清空这个节点。

  当然这里仅仅是操作步骤实现了,但我们还要验证“添加用户”窗口是否弹出,采用和第一步相同的方式,将添加用户这个窗口添加到对象库,然后生成识别代码。

  之后就可以将Step2_ClickAddUser()的检查点也实现了,具体代码如下。

 
 
public void Step2_ClickAddUser()
{
// 操作步骤:在菜单中选择添加用户
WinMenuItem uI添加用户MenuItem = this .UI系统主窗口Window.UIMenuStrip1MenuBar.UI用户管理MenuItem.UI添加用户MenuItem;
Mouse.Click(uI添加用户MenuItem,
new Point( 1 , 1 ));

// 检查点:检查添加用户子窗口是否弹出
Assert.IsTrue( this .UI添加用户Window.WaitForControlExist( 6000 ), " 点击添加用户,检测添加用户子窗口弹出失败 " );
}

  实现步骤3-6

  接下来,就是第3步到第6步的检测,3到6步基本上就只有以下几个动作。

  a.输入用户名

  b.选择用户类别

  c.填入备注

  d.点击添加按钮

  e.弹出提示框,验证文本,点击OK

  除了e步骤的“验证文本”操作的弹出框不是一个对象,其他的操作的对象都是一样的(OK这个button虽然属于不同的弹出框,但VS2010默认识别时会把它当成同一个,神奇吧),所以这部分操作代码是可以重用的,所以我们先打开录制生成器,然后录制以上的操作,并生成代码,和之前一样,这些代码都会从.designer.cs文件下剪切到.cs文件。(如何解决e步骤的“验证文本”操作的弹出框不是一个对象的问题,这个一会儿会介绍)。 

 
   
public void RecordedMethod2()
{
#region Variable Declarations
WinEdit uITbx_UserNameEdit
= this .UI添加用户Window.UITbx_UserNameWindow.UITbx_UserNameEdit;
WinComboBox uI用户类别ComboBox
= this .UI添加用户Window.UICbx_UserTypeWindow.UI用户类别ComboBox;
WinEdit uITbx_MemoEdit
= this .UI添加用户Window.UITbx_MemoWindow.UITbx_MemoEdit;
WinButton uI添加Button
= this .UI添加用户Window.UI添加Window.UI添加Button;
WinButton uIOKButton
= this .UIOKWindow.UIOKButton;
#endregion

// Type 'comeon' in 'Tbx_UserName' text box
uITbx_UserNameEdit.Text = this .RecordedMethod2Params.UITbx_UserNameEditText;

// Select '一般用户' in '用户类别:' combo box
uI用户类别ComboBox.SelectedItem = this .RecordedMethod2Params.UI用户类别ComboBoxSelectedItem;

// Type 'comeon' in 'Tbx_Memo' text box
uITbx_MemoEdit.Text = this .RecordedMethod2Params.UITbx_MemoEditText;

// Click '添加' button
Mouse.Click(uI添加Button, new Point( 52 , 17 ));

// Click 'OK' button
Mouse.Click(uIOKButton, new Point( 24 , 7 ));
}

  接下来就该解决e步骤的“验证文本”操作的弹出框不是一个对象的问题了。

  在第一章:一个简单的示例中,里面的弹出框是很难重用的,例如文本为“密码错误”和文本为“登陆成功”的弹出框,VS2010识别时会默认当他是两个对象,因此在对象库里面也是两个对象,但实际上,为了避免对象库对象过多,完全可以把它当成一个对象,只是说文本不一样,根据对象的识别原理,我们可以更改他的关键标识属性,让VS2010把所有的文本不同的弹出框都当成一个对象识别,然后在测试时把它的文本属性用来比对就OK了。

  现在我们故意让“用户名不能为空”的弹出框弹出,然后捕获上面的。

  然后可以看到对象库添加了这两个对象。

  然后我们将其保存,并生成代码,之后关闭录制生成器(一定要关闭),进入.uitest文件,寻找到对应的识别代码。

 
 
< TopLevelWindow ControlType ="Window" Id ="UI用户名不能为空Window" FriendlyName ="用户名不能为空" SpecialControlType ="None" SessionId ="920202" >
< TechnologyName > MSAA </ TechnologyName >
< WindowTitles >
< WindowTitle > 用户名不能为空 </ WindowTitle >
</ WindowTitles >
< SearchConfigurations >
< SearchConfiguration > VisibleOnly </ SearchConfiguration >
</ SearchConfigurations >
< AndCondition Id ="SearchCondition" >
< PropertyCondition Name ="Name" > 用户名不能为空 </ PropertyCondition >
< PropertyCondition Name ="ClassName" > Static </ PropertyCondition >
< PropertyCondition Name ="ControlType" > Window </ PropertyCondition >
</ AndCondition >
< SupportLevel > 1 </ SupportLevel >
< Descendants >
< UIObject ControlType ="Text" Id ="UI用户名不能为空Text" FriendlyName ="用户名不能为空" SpecialControlType ="None" >
< TechnologyName > MSAA </ TechnologyName >
< WindowTitles >
< WindowTitle > 用户名不能为空 </ WindowTitle >
</ WindowTitles >
< SearchConfigurations >
< SearchConfiguration > VisibleOnly </ SearchConfiguration >
</ SearchConfigurations >
< AndCondition Id ="SearchCondition" >
< PropertyCondition Name ="Name" > 用户名不能为空 </ PropertyCondition >
< PropertyCondition Name ="ControlType" > Text </ PropertyCondition >
</ AndCondition >
< SupportLevel > 1 </ SupportLevel >
< Descendants />
</ UIObject >
</ Descendants >
</ TopLevelWindow >

  现在我们将其进行修改,根据对象的识别原理,更改部分关键标识属性,并删掉一些意义不大的代码,让其能够识别所有不同文本的弹出框。

  修改之后的代码如下:

 
   
< TopLevelWindow ControlType ="Window" Id ="UI弹出框Window" >
< TechnologyName > 上海闵行企业网站设计与制作 MSAA </ TechnologyName >
< AndCondition Id ="SearchCondition" >
<!-- 这里删去了自动生成的<PropertyCondition Name="Name">用户名不能为空</PropertyCondition> -->
< PropertyCondition Name ="ClassName" > Static </ PropertyCondition >
< PropertyCondition Name ="ControlType" > Window </ PropertyCondition >
</ AndCondition >
< SupportLevel > 1 </ SupportLevel >
< Descendants >
< UIObject ControlType ="Text" Id ="UI弹出框Text" >
< TechnologyName > MSAA </ TechnologyName >
< AndCondition Id ="SearchCondition" >
<!-- 这里删去了自动生成的<PropertyCondition Name="Name">用户名不能为空</PropertyCondition> -->
< PropertyCondition Name ="ControlType" > Text </ PropertyCondition >
</ AndCondition >
< Descendants />
</ UIObject >
</ Descendants >
</ TopLevelWindow >

  然后我们打开录制生成器,查看修改过后的对象,可以看到连名字都变了,同时也能够识别出所有弹出框对象。

  因为目前录制生成器是从.uit上海闵行企业网站制作est文件读出来的,但是designer.cs的代码还未生成,所以根据操作动作的录制原理,我们还需要点击一次“生成代码”,然后真正起识别作用的代码就会生成到.designer.cs文件下。

  以后我们就可以通过在代码中“this.UI弹出框Window.UI弹出框Text ”来控制弹出框了。

  接下来删掉<ExecuteActions>节点中的所有内容,并将之前录制到的操作代码从.Designer.cs文件剪切到.cs文件下并进行修改,一个增加4个方法Step3_AddUserWithNoName(),Step4_AddUserWithOverlapName(),Step5_AddUserWithNoMemo(),Step6_AddUserSuccess(),修改之后的代码如下所示。

  特别注意:从step4开始检查点前面都加了一句this.mUI弹出框Window = new UI弹出框Window();之所以这么加是因为上一个操作step3已经识别了“用户名不能为空”弹出框,个人推测VS2010这时会把这个“UI弹出框Window”对象指向到“用户名不能为空”弹出框的句柄,然后虽然关闭了“用户名不能为空”弹出框,但句柄的值依然指向它,这就直接导致从step4开始的时候无法用对象“UI弹出框Window”来识别出“已有重名用户”的弹出框,所以只好将这个对象重新实例化一次。除了弹出框,其他的窗体也有这样的特点。

 
   
public void Step3_AddUserWithNoName()
{
// 操作步骤
this .UI添加用户Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "" ;
this .UI添加用户Window.UICbx_UserTypeWindow.UI用户类别ComboBox.SelectedItem = " 一般用户 " ;
this .UI添加用户Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "" ;
Mouse.Click(
this .UI添加用户Window.UI添加Window.UI添加Button, new Point( 1 , 1 ));

// 检查点:首先检查3秒内是否有弹出框弹出,如果有,则检查文本是否是"用户名不能为空"
bool isPopUp = this .UI弹出框Window.UI弹出框Text.WaitForControlExist( 3000 );
Assert.IsTrue(isPopUp上海徐汇企业网站制作pan>&&this.UI弹出框Window.UI弹出框Text.DisplayText == "用户名不能为空", "输入用户名为空,检测弹出“用户名不能为空”失败");
Mouse.Click(
this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step4_AddUserWithOverlapName()
{
//操作步骤
this.UI添加用户Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser1";
this.UI添加用户Window.UICbx_UserTypeWindow.UI用户类别ComboBox.SelectedItem = "一般用户";
this.UI添加用户Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "";
Mouse.Click(
this.UI添加用户Window.UI添加Window.UI添加Button, new Point(1, 1));

//检查点:首先检查3秒内是否有弹出框弹出,如果有,则检查文本是否是"已有重名用户"
this.mUI弹出框Window = new UI弹出框Window();
bool isPopUp = this.UI弹出框Window.UI弹出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp
&&this.UI弹出框Window.UI弹出框Text.DisplayText == "已有重名用户", "输入重名用户,检测弹出“已有重名用户”失败");
Mouse.Click(
this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step5_AddUserWithNoMemo()
{
//操作步骤
this.UI添加用户Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser6";
this.UI添加用户Window.UICbx_UserTypeWindow.UI用户类别ComboBox.SelectedItem = "一般用户";
this.UI添加用户Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "";
Mouse.Click(
this.UI添加用户Window.UI添加Window.UI添加Button, new Point(1, 1));

//检查点:首先检查3秒内是否有弹出框弹出,如果有,则检查文本是否是"备注不能为空"
this.mUI弹出框Window = new UI弹出框Window();
bool isPopUp = this.UI弹出框Window.UI弹出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp
&&this.UI弹出框Window.UI弹出框Text.DisplayText == "备注不能为空", "输入备注为空,检测弹出“备注不能为空”失败");
Mouse.Click(
this.UIOKWindow.UIOKButton, new Point(1, 1));
}

public void Step6_AddUserSuccess()
{
//操作步骤
this.UI添加用户Window.UITbx_UserNameWindow.UITbx_UserNameEdit.Text = "TestUser6";
this.UI添加用户Window.UICbx_UserTypeWindow.UI用户类别ComboBox.SelectedItem = "一般用户";
this.UI添加用户Window.UITbx_MemoWindow.UITbx_MemoEdit.Text = "Test";
Mouse.Click(
this.UI添加用户Window.UI添加Window.UI添加Button, new Point(1, 1));

//检查点:首先检查3秒内是否有弹出框弹出,如果有,则检查文本是否是"添加成功!"
this.mUI弹出框Window = new UI弹出框Window();
bool isPopUp = this.UI弹出框Window.UI弹出框Text.WaitForControlExist(3000);
Assert.IsTrue(isPopUp
&&this.UI弹出框Window.UI弹出框Text.DisplayText == "添加成功!", "输入正确,检测弹出“添加成功!”失败");
Mouse.Click(
this.UIOKWindow.UIOKButton, new Point(1, 1));
}

  实现步骤7

  现在就只差最后一步了,就是关闭两个窗体,采用之前的方式,先录制关闭操作,先让操作代码生成在Designer.cs,然后在把它剪切到.cs下,最后添加验证逻辑,录制就不详细说明了,最终代码如下:

 
   
public void Step7_CloseWindows()
{
// 操作步骤
bool isClosed;
Mouse.Click(
this .UI添加用户Window.UI取消Window.UI取消Button, new Point( 1 , 1 ));
// 和WaitForControlExist相反,这里是最长等待他3秒关闭,如果3秒内关闭返回true,否则为false
isClosed = this .UI添加用户Window.WaitForControlNotExist( 3000 );
Mouse.Click(
this 上海网站建设yle="color: #000000;">.UI系统主窗口Window.UI系统主窗口TitleBar.UICloseButton, new Point( 1 , 1 ));
isClosed
&= this .UI系统主窗口Window.WaitForControlNotExist( 3000 );

// 检查点
Assert.IsTrue(isClosed, " 点击退出,检测是否退出添加用户子窗体和主窗体失败 " );
}

  总结

  在本章上部分,介绍了通过编码的方式来建立UI测试,在这里首先实现了添加用户窗体上的操作,相信大家在看完了以后,应该能够对UI测试有一个更深的认识了,如果需要调用刚才所写的方法,只需要再新建一个编码的UI测试,例如将其命名为CodedUITest1.cs,然后在内部添加一个这样的调用即可。

 
   
[TestMethod]
public void CodedUITestMethod1()
{
AddUserUIMapClasses.AddUserUIMap uimap
= new AddUserUIMapClasses.AddUserUIMap();
uimap.Step1_LoginSystem();
uimap.Step2_ClickAddUser();
uimap.Step3_AddUserWithNoName();
uimap.Step4_AddUserWithOverlapName();
uimap.Step5_AddUserWithNoMemo();
uimap.Step6_AddUserSuccess();
uimap.Step7_CloseWindows();
}

  示例程序的下载:下载点我

  个人录制的源代码下载:下载点我

  下部分将介绍查询用户窗体的测试代码的编写,以及他们测试的关联。

目录
相关文章
|
2月前
|
测试技术 持续交付 API
深入挖掘探索.NET单元测试
【10月更文挑战第11天】
39 2
|
2月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
29 1
|
2月前
|
测试技术 API 开发者
精通.NET单元测试:MSTest、xUnit、NUnit全面解析
【10月更文挑战第15天】本文介绍了.NET生态系统中最流行的三种单元测试框架:MSTest、xUnit和NUnit。通过示例代码展示了每种框架的基本用法和特点,帮助开发者根据项目需求和个人偏好选择合适的测试工具。
41 3
|
23天前
|
开发框架 安全 .NET
.NET使用Moq开源模拟库简化单元测试
.NET使用Moq开源模拟库简化单元测试~
|
2月前
|
前端开发 JavaScript C#
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
|
4月前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
51 8
|
4月前
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
368 8
|
4月前
|
Kubernetes 监控 Devops
【独家揭秘】.NET项目中的DevOps实践:从代码提交到生产部署,你不知道的那些事!
【8月更文挑战第28天】.NET 项目中的 DevOps 实践贯穿代码提交到生产部署全流程,涵盖健壮的源代码管理、GitFlow 工作流、持续集成与部署、容器化及监控日志记录。通过 Git、CI/CD 工具、Kubernetes 及日志框架的最佳实践应用,显著提升软件开发效率与质量。本文通过具体示例,助力开发者构建高效可靠的 DevOps 流程,确保项目成功交付。
85 0
|
4月前
|
XML 开发框架 .NET
.NET框架:软件开发领域的瑞士军刀,如何让初学者变身代码艺术家——从基础架构到独特优势,一篇不可错过的深度解读。
【8月更文挑战第28天】.NET框架是由微软推出的统一开发平台,支持多种编程语言,简化应用程序的开发与部署。其核心组件包括公共语言运行库(CLR)和类库(FCL)。CLR负责内存管理、线程管理和异常处理等任务,确保代码稳定运行;FCL则提供了丰富的类和接口,涵盖网络、数据访问、安全性等多个领域,提高开发效率。此外,.NET框架还支持跨语言互操作,允许开发者使用C#、VB.NET等语言编写代码并无缝集成。这一框架凭借其强大的功能和广泛的社区支持,已成为软件开发领域的重要工具,适合初学者深入学习以奠定职业生涯基础。
108 1
|
4月前
|
API
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)