Caliburn.Micro单元测试
我正在做MVVM中的第一步。Caliburn.Micro单元测试
我使用Caliburn.Micro作为我的MVVM框架。
我有以下代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Globalization;
using Caliburn.Micro;
namespace MY_PROJECT.ViewModels
{
[Export(typeof(MainViewModel))]
public class MainViewModel : Screen
{
private readonly IWindowManager _windowManager = null;
private readonly IEventAggregator _events;
private string _title = string.Empty;
private WindowState _windowState = WindowState.Normal;
public string Title
{
get
{
return _title;
}
set
{
_title = value;
NotifyOfPropertyChange(() => Title);
}
}
public WindowState WindowState
{
get
{
return _windowState;
}
set
{
_windowState = value;
NotifyOfPropertyChange(() => WindowState);
}
}
public void ChangeTittle(string title)
{
Title = title;
}
public void ToggleWindowState()
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
[ImportingConstructor]
public MainViewModel(IWindowManager windowManager, IEventAggregator events)
{
_windowManager = windowManager;
_events = events;
_events.Subscribe(this);
}
}
我现在想写一些简单的单元测试来测试我的视图模型。
如何做到这一点的任何建议?
Caliburn.Micro的文档似乎缺少这些信息。
好的,你可以通过IoC容器运行整个应用程序并引导整个事物,这样你就可以获得所有接口的实际具体实现。缺点是你需要一切可用的东西(数据库,Web服务等),如果你几乎需要运行一个应用程序,可能会更难测试一个应用程序(而且由于实际工作正在进行,所以执行测试可能会慢得多完成)。如果你可以轻松走这条路,那么一定要用它。
另外,您可以通过使用模拟框架嘲笑使用嘲笑或存根对象的行为/状态,如起订量
随着起订量,你设置你的测试环境,使您的接口和类由Mock<T>
代表(模拟对象),您指定其行为。然后您测试对这种行为在ViewModels
下面是它为您MainViewModel
设置的使用起订量和NUnit测试的一个例子是目前的化身:
// Decorate with test fixture attribute so NUnit knows it's a test
[TestFixture]
class MainViewModelTests
{
// The interfaces/instances you will need to test with - this is your test subject
MainViewModel _mainVM;
// You can mock the other interfaces:
Mock<IWindowManager> _windowManager;
Mock<IEventAggregator> _eventAggregator;
// Setup method will run at the start of each test
[SetUp]
public void Setup()
{
// Mock the window manager
_windowManager = new Mock<IWindowManager>();
// Mock the event aggregator
_windowManager = new Mock<IEventAggregator>();
// Create the main VM injecting the mocked interfaces
// Mocking interfaces is always good as there is a lot of freedom
// Use mock.Object to get hold of the object, the mock is just a proxy that decorates the original object
_mainVM = new MainViewModel(_windowManager.Object, _eventAggregator.Object);
}
// Create a test to make sure the VM subscribes to the aggregator (a GOOD test, I forget to do this a LOT and this test gives me a slap in the face)
[Test]
public void Test_SubscribedToEventAggregator()
{
// Test to make sure subscribe was called on the event aggregator at least once
_eventAggregator.Verify(x => x.Subscribe(_mainVM));
}
// Check that window state toggles ok when it's called
[Test]
public void Test_WindowStateTogglesCorrectly()
{
// Run the aggregator test at the start of each test (this will run as a 'child' test)
Test_SubscribedToEventAggregator();
// Check the default state of the window is Normal
Assert.True(_mainVM.WindowState == WindowState.Normal);
// Toggle it
_mainVM.ToggleWindowState();
// Check it's maximised
Assert.True(_mainVM.WindowState == WindowState.Maximised);
// Check toggle again for normal
_mainVM.ToggleWindowState();
Assert.True(_mainVM.WindowState == WindowState.Normal);
}
// Test the title changes correctly when the method is called
[Test]
public void Test_WindowTitleChanges()
{
Test_SubscribedToEventAggregator();
_mainVM.ChangeTitle("test title");
Assert.True(_mainVM.Title == "test title");
}
}
你可以看到你如何测试状态和行为,我当调用虚拟机方法(如ChangeTitle
)时,预计会出现某种虚拟机状态,并且我还预计会出现一种行为(我预计在每次测试开始时至少会在聚合器上调用Subscribe(X)
一次)。
装饰有[SetUp]
的方法将在每次测试开始时调用。有拆卸和其他方法(包括一个设置整个测试夹具,即每个夹具只能运行一次)
这里的关键在于对于CM,您实际上并不需要嘲笑事件中的任何行为因为CM期望您为您的事件聚合器消息实施IHandle<T>
。使这些订户方法成为接口实现意味着您已经拥有了可以调用的模拟事件聚合器调用的对象的公共方法。
因此,例如,你可以使用
public class MainViewModel : IHandle<someEventMessageArgument> // (this is your subscriber interface)
{
// (and this is the corresponding method)
public void Handle(someEventMessageArgument message)
{
// do something useful maybe change object state or call some methods
}
}
// Test the method - you don't need to mock any event aggregator behaviour since you have tested that the VM was subscribed to the aggregator. (OK CM might be broken but that's Robs problem :))
[Test]
Test_SomeEventDoesWhatYouAreExpecting()
{
_mainVM.Handle(someEventMessageArgument);
// Assert that what was supposed to happen happened...
Assert.True(SomethingHappened);
}
退房起订量快速入门这里:
非常感谢!我实际上把NSubstitute看作是一个模拟库,但moq似乎也在做这项工作。 – user2070238 2013-03-25 12:27:03
Charleh,即使在模拟环境下,单元测试仍然需要加载Caliburn.Micro程序集才能访问IEventAggregator接口。加载该程序集可能会给单元测试框架带来很大的负担吗?我只是意识到测试应该是轻量级和快速的。 – 2014-07-11 14:46:57
对这个评论的反应迟了一点,但是没有,你的应用程序可能比CM首先要大得多,这个框架非常小。我的测试运行速度非常快 – Charleh 2015-10-26 10:18:19
单元测试是Caliburn.Micro的范围之内,因为该框架是一个MVVM框架和不是一个单元测试框架。它允许您轻松进行单元测试,它不提供任何种类的单元测试界面。你可能想要一个快速的Google。我可以给你一些流行的测试框架 - NUnit非常流行,并且有很多教程,MSTest是集成到Visual Studio中的Microsofts产品之一。查看http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#.NET_programming_languages – Charleh 2013-03-23 21:51:55
我知道Caliburn.Micro不是一个单元测试框架。我的计划实际上是使用xUnit作为单元测试框架。 但是,我不明白创建测试的过程是什么。例如,我需要做的第一件事是初始化视图模型。但是,我如何提供windowManager和构造器所需的事件?我是否还需要初始化引导程序?这些是我没有得到的东西。 – user2070238 2013-03-24 09:33:30
您要么在您的测试设置方法中提供这些实例,要么使用mock来模拟所需接口或类型的状态或行为。看看Moq或其他一些嘲讽框架的信息 – Charleh 2013-03-24 11:04:10