使用 NUnit Test Runner 测试命令和导航的简单 Specflow
我们为什么需要这个?
目前在 Xamarin.Forms 中进行单元测试的方法是通过平台运行,因此你的测试必须在 ios,android,windows 或 mac UI 环境中运行 : 在 IDE 中运行测试
Xamarin 还使用 Xamarin.TestCloud 产品提供了令人敬畏的 UI 测试,但是当想要实现 BDD 开发实践,并且能够测试 ViewModel 和 Commands,同时在本地或构建服务器上的单元测试运行器上廉价运行时,没有建成的方式。
我开发了一个库,允许使用 Specflow 和 Xamarin.Forms 轻松实现从 Scenarios 定义到 ViewModel 的功能,独立于用于 App 的任何 MVVM 框架(例如 XLabs , MVVMCross , Prism )
如果你是 BDD 的新手 ,请检查 Specflow out。
用法:
-
如果你还没有,请从此处(或从你的 visual studio IDE)安装 specflow visual studio 扩展: https ://visualstudiogallery.msdn.microsoft.com/c74211e7-cb6e-4dfa-855d-df0ad4a37dd6
-
将类库添加到 Xamarin.Forms 项目中。那是你的测试项目。
-
将 nuget 中的 SpecFlow.Xamarin.Forms 包添加到你的测试项目中。
-
向你继承
TestApp
的测试项目添加一个类,并注册你的 views / viewmodels 对以及添加任何 DI 注册,如下所示:
public class DemoAppTest : TestApp
{
protected override void SetViewModelMapping()
{
TestViewFactory.EnableCache = false;
// register your views / viewmodels below
RegisterView<MainPage, MainViewModel>();
}
protected override void InitialiseContainer()
{
// add any di registration here
// Resolver.Instance.Register<TInterface, TType>();
base.InitialiseContainer();
}
}
- 将 SetupHook 类添加到测试项目中,以便为你添加 Specflow 挂钩。你将需要按照以下方式引导测试应用程序,提供你在上面创建的类以及你的应用程序初始视图模型:
[Binding]
public class SetupHooks : TestSetupHooks
{
/// <summary>
/// The before scenario.
/// </summary>
[BeforeScenario]
public void BeforeScenario()
{
// bootstrap test app with your test app and your starting viewmodel
new TestAppBootstrap().RunApplication<DemoAppTest, MainViewModel>();
}
}
- 你需要在你的 xamarin.forms 视图代码隐藏中添加一个 catch 块,以便忽略 xamarin.forms 框架,迫使你运行 app ui(我们不想做的事情):
public YourView()
{
try
{
InitializeComponent();
}
catch (InvalidOperationException soe)
{
if (!soe.Message.Contains("MUST"))
throw;
}
}
-
为项目添加 specflow 功能(使用 vs specflow 扩展附带的 vs specflow 模板)
-
创建/生成继承 TestStepBase 的步骤类,将 scenarioContext 参数传递给基类。
-
使用导航服务和帮助程序来导航,执行命令和测试视图模型:
[Binding]
public class GeneralSteps : TestStepBase
{
public GeneralSteps(ScenarioContext scenarioContext)
: base(scenarioContext)
{
// you need to instantiate your steps by passing the scenarioContext to the base
}
[Given(@"I am on the main view")]
public void GivenIAmOnTheMainView()
{
Resolver.Instance.Resolve<INavigationService>().PushAsync<MainViewModel>();
Resolver.Instance.Resolve<INavigationService>().CurrentViewModelType.ShouldEqualType<MainViewModel>();
}
[When(@"I click on the button")]
public void WhenIClickOnTheButton()
{
GetCurrentViewModel<MainViewModel>().GetTextCommand.Execute(null);
}
[Then(@"I can see a Label with text ""(.*)""")]
public void ThenICanSeeALabelWithText(string text)
{
GetCurrentViewModel<MainViewModel>().Text.ShouldEqual(text);
}
}