如何创建可单元测试的 Silverlight 解决方案






4.50/5 (4投票s)
本文介绍了编写高度可进行单元测试的 Silverlight 应用程序的最佳实践。
引言
单元测试用于验证函数或函数集是否“遵守其契约”——换句话说,就是被测试的函数(或函数集)是否满足要求。单元测试会检查黑盒和白盒。
本文不讨论什么是单元测试或如何编写单元测试用例。网上有很多相关的文章。 这里有一个很好的链接,您可以从中获取有关单元测试的信息。
开发者为了实现功能而直接在 XAML 代码隐藏文件中编写代码是很常见的,但这会降低应用程序的可测试性。本文将介绍如何编写高度可测试的代码。
背景

我从 PRISM 框架的 Stock Trader 示例应用程序中获得了这个应用程序的灵感。该应用程序有两个网格。第一个网格列出了所有可用的股票。用户可以选择一只股票并将其添加到观察列表中。第二个网格包含用户想要观察的股票。用户可以从观察列表中选择一只股票,然后单击“移除”将其从观察列表中删除。用户可以在搜索文本框中输入文本并单击“搜索”按钮来搜索股票。
这里我们不关注应用程序及其 UI 方面。我们只关注应用程序的单元测试。
Using the Code
随附的 zip 文件包含两个 Visual Studio 解决方案。
- UnitTestDemoApp\UnitTestDemo\UnitTestDemo.sln:这是理想的测试解决方案。XAML 代码隐藏中几乎没有代码。
- UnitTestDemoStart\UnitTestDemo\UnitTestDemo.sln:这是非常糟糕的测试解决方案。几乎所有的代码都在代码隐藏中。此解决方案无法进行单元测试。
两个解决方案都包含以下项目
UnitTestDemo
:Silverlight 应用程序UnitTestDemo.Web
:托管 Silverlight 应用程序的 Web 应用程序UnitTestDemoTests
:Silverlight 应用程序的测试项目
以下是解决方案中使用的不同类
PositionSummaryItem
:包含股票的详细信息。AccountPositionService
:服务类,用于从 XML 文件检索股票详细信息。CurrencyConverter
:用于将值转换为美元格式的转换器。Silverlight 4.0 支持此功能。我们可以在 Silverlight 4.0 中指定格式。DecimalToColorConverter
:如果值为负数或正数,则用红色或绿色背景色显示值的转换器。PercentConverter
:用于将值显示为百分比的转换器。
Using the Code
不可测试的应用程序
这是 XAML 类的代码隐藏代码。如果您查看此代码,会发现我们直接从视图调用服务并更改网格的源、过滤记录、启用/禁用按钮。由于所有方法都是 `private` 的,我们无法编写单元测试用例。
public partial class StockDetailsVeiw : UserControl
{
private string searchString = string.Empty;
private List<positionsummaryitem> stocks = new List<positionsummaryitem>();
private List<positionsummaryitem> currentStocks = new List<positionsummaryitem>();
private ObservableCollection<positionsummaryitem> watchList =
new ObservableCollection<positionsummaryitem>();
public StockDetailsVeiw()
{
InitializeComponent();
this.btnAddStockToWatchlist.Click +=
new RoutedEventHandler(btnAddStockToWatchlist_Click);
this.btnRemoveStockFromWattchList.Click +=
new RoutedEventHandler(btnRemoveStockFromWattchList_Click);
this.btnSearch.Click += new RoutedEventHandler(btnSearch_Click);
this.PositionsGrid.SelectionChanged +=
new SelectionChangedEventHandler(PositionsGrid_SelectionChanged);
this.watchListGrid.SelectionChanged +=
new SelectionChangedEventHandler(watchListGrid_SelectionChanged);
this.txtSearch.TextChanged +=
new TextChangedEventHandler(txtSearch_TextChanged);
}
private void txtSearch_TextChanged(object sender, TextChangedEventArgs e)
{
this.btnSearch.IsEnabled =
this.txtSearch.Text.Trim().Length == 0 ? false : true;
}
private void watchListGrid_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
this.btnRemoveStockFromWattchList.IsEnabled =
this.watchListGrid.SelectedItem == null ? false : true;
}
private void PositionsGrid_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
this.btnAddStockToWatchlist.IsEnabled =
this.PositionsGrid.SelectedItem == null ? false : true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
AccountPositionService service = new AccountPositionService();
this.stocks = service.GetAccountPositions();
this.currentStocks = this.stocks;
this.PositionsGrid.ItemsSource = this.currentStocks;
this.watchListGrid.ItemsSource = this.watchList;
this.btnSearch.IsEnabled = false;
this.btnAddStockToWatchlist.IsEnabled = false;
this.btnRemoveStockFromWattchList.IsEnabled = false;
}
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
string stringSearch = this.txtSearch.Text.Trim();
if (!string.IsNullOrEmpty(stringSearch))
{
this.currentStocks = (from stock in this.stocks
where stock.TickerSymbol.ToLower().Contains
(stringSearch.ToLower())
select stock).ToList();
this.PositionsGrid.ItemsSource = this.currentStocks;
}
}
private void btnRemoveStockFromWattchList_Click(object sender, RoutedEventArgs e)
{
if (this.watchListGrid.SelectedItem != null && this.watchList != null &&
this.watchList.Contains
((PositionSummaryItem)this.watchListGrid.SelectedItem))
{
this.watchList.Remove((PositionSummaryItem)
this.watchListGrid.SelectedItem);
}
}
private void btnAddStockToWatchlist_Click(object sender, RoutedEventArgs e)
{
if (this.PositionsGrid.SelectedItem != null && this.watchList != null &&
!this.watchList.Contains((PositionSummaryItem)
this.PositionsGrid.SelectedItem))
{
this.watchList.Add((PositionSummaryItem)this.PositionsGrid.SelectedItem);
}
}
可测试的代码
编写可测试代码的基础是遵循特定的模式。我们可以根据项目和需求在 MVVM、MVM 和 MVC 中选择一种模式。MVVM 主要用于 Silverlight 应用程序。在此应用程序中,我遵循了 MVVM 模式,但您也可以遵循其他模式。
您可以在以下 URL 中找到不同模式的定义
- http://www.apexa.net/Blog/web_design_Blog_20090112.aspx
- http://www.silverlightkb.com/Default.aspx?Page=MVC%2c%20MVP%20and%20MVVM&AspxAutoDetectCookieSupport=1
以下是 XAML 类的代码隐藏代码
public partial class StockDetailsVeiw : UserControl, IStockDetailsView
{
public StockDetailsVeiw()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
StockDetailsPresentationModel model =
new StockDetailsPresentationModel(this, new AccountPositionService());
//this.btnSearch.Click += new RoutedEventHandler(btnSearch_Click);
}
void btnSearch_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
#region IStockDetailsView Members
public IStockDetailsPresentationModel Model
{
get
{
return this.DataContext as IStockDetailsPresentationModel;
}
set
{
this.DataContext = value;
}
}
#endregion
}
您可以看到,代码隐藏中几乎没有代码,除了 Model 属性和视图模型的初始化。该应用程序使用绑定从 ViewModel 获取数据。我将业务逻辑与视图分离,并将其写在 ViewModel 中,我们可以单独测试 ViewModel。我们可以为完全不同的 UI 使用相同的 ViewModel,但功能相同。我们无需对 `ViewModel` 进行任何更改。
以下是 ViewModel 的代码
public class StockDetailsPresentationModel :
IStockDetailsPresentationModel, INotifyPropertyChanged
{
Dictionary<string, PositionSummaryItem> test =
new Dictionary<string, PositionSummaryItem>();
private string searchString = string.Empty;
private PositionSummaryItem selectedStock;
private PositionSummaryItem watchListSelectedStock;
private List<PositionSummaryItem> stocks = new List<PositionSummaryItem>();
private List<PositionSummaryItem> currentStocks = new List<PositionSummaryItem>();
private ObservableCollection<PositionSummaryItem> watchList =
new ObservableCollection<PositionSummaryItem>();
private DelegateCommand<string> submitCommand;
private DelegateCommand<object> addStockToWatchList;
private DelegateCommand<object> removeStockFromWatchList;
public event PropertyChangedEventHandler PropertyChanged;
private IAccountPositionService accountService;
public StockDetailsPresentationModel(IStockDetailsView view,
IAccountPositionService accountService)
{
View = view;
View.Model = this;
this.accountService = accountService;
this.AddStockToWatchList = new DelegateCommand<object>
(this.AddStockToWatchListExecute, this.AddStockToWatchListCanExecute);
this.RemoveStockFromWatchList = new DelegateCommand<object>
(this.RemoveStockToWatchListExecute, this.RemoveStockToWatchListCanExecute);
this.stocks = this.accountService.GetAccountPositions();
this.CurrentStocks = this.stocks;
this.SubmitCommand = new DelegateCommand<string>
(SubmitCommandExecuted, SubmitCommandCanExecute);
}
public Dictionary<string, PositionSummaryItem> Dictionary
{
get
{
return this.test;
}
set
{
this.test = value;
this.InvokePropertyChanged("Dictionary");
}
}
public IStockDetailsView View { get; set; }
public List<PositionSummaryItem> CurrentStocks
{
get
{
return currentStocks;
}
set
{
this.currentStocks = value;
this.InvokePropertyChanged("CurrentStocks");
}
}
public string SearchString
{
get
{
return searchString;
}
set
{
if (value != searchString)
{
searchString = value;
InvokePropertyChanged("SearchString");
this.submitCommand.RaiseCanExecuteChanged();
}
}
}
public ICommand SubmitCommand
{
get
{
return submitCommand;
}
set
{
this.submitCommand = (DelegateCommand<string>)value;
this.InvokePropertyChanged("SubmitCommand");
}
}
public ICommand RemoveStockFromWatchList
{
get
{
return removeStockFromWatchList;
}
set
{
this.removeStockFromWatchList = (DelegateCommand<object>)value;
this.InvokePropertyChanged("RemoveStockFromWatchList");
}
}
public ICommand AddStockToWatchList
{
get
{
return addStockToWatchList;
}
set
{
this.addStockToWatchList = (DelegateCommand<object>)value;
this.InvokePropertyChanged("AddStockToWatchList");
}
}
public PositionSummaryItem SelectedStock
{
get
{
return this.selectedStock;
}
set
{
this.selectedStock = value;
this.InvokePropertyChanged("SelectedStock");
this.addStockToWatchList.RaiseCanExecuteChanged();
}
}
public PositionSummaryItem WatchListSelectedStock
{
get
{
return this.watchListSelectedStock;
}
set
{
this.watchListSelectedStock = value;
this.InvokePropertyChanged("WatchListSelectedStock");
this.removeStockFromWatchList.RaiseCanExecuteChanged();
}
}
public ObservableCollection<PositionSummaryItem> WatchList
{
get
{
return this.watchList;
}
set
{
this.watchList = value;
this.InvokePropertyChanged("WatchList");
}
}
private void InvokePropertyChanged(string property)
{
PropertyChangedEventHandler Handler = PropertyChanged;
if (Handler != null)
{
Handler(this, new PropertyChangedEventArgs(property));
}
}
private void SubmitCommandExecuted(string parameter)
{
this.CurrentStocks = (from item in this.stocks
where item.TickerSymbol.ToLower().Contains(parameter.ToLower())
select item).ToList();
}
private void AddStockToWatchListExecute(object stock)
{
if (this.SelectedStock != null && this.WatchList != null &&
!this.WatchList.Contains(this.SelectedStock))
{
this.WatchList.Add(this.SelectedStock);
}
}
private void RemoveStockToWatchListExecute(object stock)
{
if (this.WatchListSelectedStock != null && this.WatchList != null &&
this.WatchList.Contains(this.WatchListSelectedStock))
{
this.WatchList.Remove(this.WatchListSelectedStock);
}
}
private bool AddStockToWatchListCanExecute(object parameter)
{
if (this.SelectedStock == null)
{
return false;
}
else
{
return true;
}
}
private bool RemoveStockToWatchListCanExecute(object parameter)
{
if (this.WatchListSelectedStock == null)
{
return false;
}
else
{
return true;
}
}
private bool SubmitCommandCanExecute(string parameter)
{
if (this.SearchString == null || this.searchString == string.Empty)
{
return false;
}
else
{
return true;
}
}
}
您可以看到不同按钮的命令,当用户单击按钮时会触发这些命令。即使不使用任何 UI 元素(甚至不使用我们要启用/禁用的按钮),我们也可以启用/禁用按钮。Silverlight 3.0 不支持命令。我为此编写了一个类。Silverlight 4.0 支持命令。
您还应该注意的一点是,我在所有地方都使用了接口。我通过接口来交换对象。这有助于编写测试用例。单元测试的基础是一次测试一个类。如果我们正在测试一个特定的类,那么所有其他类都应该被模拟。接口有助于创建模拟类。
如果您查看 `StockDetailsVeiw` 类的 `UserControl_Loaded` 方法,我们在初始化 `StockDetailsPresentationModel` 对象时传递了视图对象,但是 `StockDetailsPresentationModel` 以 `IStockDetailsView` 的形式接受 `StockDetailsView` 对象。我们这样做是为了在测试模型时轻松模拟视图对象。
如果您正在开发一个更大的应用程序,并希望通过使用接口来自动化此对象初始化,则可以使用 Unity Application Block。有关 Unity Application Block 的更多详细信息,请参阅以下链接
[TestClass]
public class StockDetailsPresentationModelFixture
{
private MockAccountPositionService accountPositionService;
private MockStocksDetailsView stocksDetailsView;
[TestInitialize]
public void Setup()
{
stocksDetailsView = new MockStocksDetailsView();
accountPositionService = new MockAccountPositionService();
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenDictionaryUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.Dictionary = new Dictionary<string, PositionSummaryItem>();
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("Dictionary", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenCurrentStocksUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
List<PositionSummaryItem> list = new List<PositionSummaryItem>();
list.Add(new PositionSummaryItem());
presentationModel.CurrentStocks = list;
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("CurrentStocks", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenSearchStringUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.SearchString = "TestSearchString";
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("SearchString", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenSubmitCommandUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.SubmitCommand = new DelegateCommand<string>(this.TempMethod);
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("SubmitCommand", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenAddStockToWatchListUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.AddStockToWatchList =
new DelegateCommand<object>(this.TempMethod);
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("AddStockToWatchList", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenSelectedStockUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.SelectedStock = new PositionSummaryItem();
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("SelectedStock", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenWatchListSelectedStockUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.WatchListSelectedStock = new PositionSummaryItem();
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("WatchListSelectedStock", propertyChangedEventArgsArgument);
}
[TestMethod]
public void ShouldRaiseOnPropertyChangedEventWhenWatchListUpdated()
{
bool propertyChangedRaised = false;
string propertyChangedEventArgsArgument = null;
var presentationModel = this.CreatePresentationModel();
presentationModel.PropertyChanged += (sender, e) =>
{
propertyChangedRaised = true;
propertyChangedEventArgsArgument = e.PropertyName;
};
Assert.IsFalse(propertyChangedRaised);
Assert.IsNull(propertyChangedEventArgsArgument);
presentationModel.WatchList = new ObservableCollection<PositionSummaryItem>();
Assert.IsTrue(propertyChangedRaised);
Assert.AreEqual("WatchList", propertyChangedEventArgsArgument);
}
[TestMethod]
public void
ShouldNotThrowExceptionIfWatchListIsNullWhileExecutingAddStockToWatchListCommand()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SelectedStock = null;
presentationModel.AddStockToWatchList.Execute(null);
}
[TestMethod]
public void
ShouldNotThrowExceptionIfSelectedStockIsNullWhileExecutingAddStockToWatchListCommand()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SelectedStock = new PositionSummaryItem();
presentationModel.WatchList = null;
presentationModel.AddStockToWatchList.Execute(null);
}
[TestMethod]
public void ShouldAddStockInWatchListIfSelectedStockIsDoesNotExistInWatchList()
{
var presentationModel = this.CreatePresentationModel();
var selectedStock = new PositionSummaryItem();
presentationModel.SelectedStock = selectedStock;
Assert.IsTrue(0 == presentationModel.WatchList.Count);
presentationModel.AddStockToWatchList.Execute(null);
Assert.IsTrue(1 == presentationModel.WatchList.Count);
Assert.AreSame(selectedStock, presentationModel.WatchList[0]);
}
[TestMethod]
public void ShouldNotAddStockInWatchListIfSelectedStockAlreadyExistInWatchList()
{
var presentationModel = this.CreatePresentationModel();
var selectedStock = new PositionSummaryItem();
presentationModel.SelectedStock = selectedStock;
presentationModel.WatchList.Add(selectedStock);
Assert.IsTrue(1 == presentationModel.WatchList.Count);
presentationModel.AddStockToWatchList.Execute(null);
Assert.IsTrue(1 == presentationModel.WatchList.Count);
}
[TestMethod]
public void
ShouldNotThrowExceptionIfWatchListIsNullWhileExecutingRemoveStockToWatchListCommand()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.WatchListSelectedStock = null;
presentationModel.RemoveStockFromWatchList.Execute(null);
}
[TestMethod]
public void
ShouldNotThrowExceptionIfSelectedStockIsNullWhileExecutingRemoveStockToWatchListCommand()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SelectedStock = new PositionSummaryItem();
presentationModel.WatchListSelectedStock = null;
presentationModel.RemoveStockFromWatchList.Execute(null);
}
[TestMethod]
public void ShouldRemoveSelectedStockFromWatchListIfItExistsInWatchList()
{
var presentationModel = this.CreatePresentationModel();
var selectedStock = new PositionSummaryItem();
presentationModel.WatchListSelectedStock = selectedStock;
presentationModel.WatchList.Add(selectedStock);
presentationModel.RemoveStockFromWatchList.Execute(null);
Assert.IsTrue(0 == presentationModel.WatchList.Count);
}
[TestMethod]
public void ShouldNotRemoveSelectedStockFromWatchListIfItDoesNotExistsInWatchList()
{
var presentationModel = this.CreatePresentationModel();
var selectedStock = new PositionSummaryItem();
presentationModel.SelectedStock = selectedStock;
presentationModel.WatchList.Add(new PositionSummaryItem());
Assert.IsTrue(1 == presentationModel.WatchList.Count);
presentationModel.RemoveStockFromWatchList.Execute(null);
Assert.IsTrue(1 == presentationModel.WatchList.Count);
}
[TestMethod]
public void AddStockToWatchListCanExecuteShouldReturnFalseIfSelectedStockIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SelectedStock = null;
Assert.IsFalse(presentationModel.AddStockToWatchList.CanExecute(null));
}
[TestMethod]
public void AddStockToWatchListCanExecuteShouldReturnTrueIfSelectedStockIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SelectedStock = new PositionSummaryItem();
Assert.IsTrue(presentationModel.AddStockToWatchList.CanExecute(null));
}
[TestMethod]
public void
RemoveStockToWatchListCanExecuteShouldReturnTrueIfWatchListSelectedStockIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.WatchListSelectedStock = new PositionSummaryItem();
Assert.IsTrue(presentationModel.RemoveStockFromWatchList.CanExecute(null));
}
[TestMethod]
public void
RemoveStockToWatchListCanExecuteShouldReturnFalseIfWatchListSelectedStockIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.WatchListSelectedStock = null;
Assert.IsFalse(presentationModel.RemoveStockFromWatchList.CanExecute(null));
}
[TestMethod]
public void SubmitCommandCanExecuteShouldReturnTrueIfSearchStringIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SearchString = "test";
Assert.IsTrue(presentationModel.SubmitCommand.CanExecute(null));
}
[TestMethod]
public void SubmitCommandCanExecuteShouldReturnFalseIfSearchStringIsNull()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SearchString = null;
Assert.IsFalse(presentationModel.SubmitCommand.CanExecute(null));
}
[TestMethod]
public void SubmitCommandCanExecuteShouldReturnFalseIfSearchStringIsEmpty()
{
var presentationModel = this.CreatePresentationModel();
presentationModel.SearchString = string.Empty;
Assert.IsFalse(presentationModel.SubmitCommand.CanExecute(null));
}
[TestMethod]
public void SubmitCommandExecutedShouldFilterTheRecords()
{
List<PositionSummaryItem> accountSummaryList = new List<PositionSummaryItem>();
accountSummaryList.Add(new PositionSummaryItem(){TickerSymbol = "stock1"});
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "microsoft" });
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "office" });
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock2" });
this.accountPositionService.List = accountSummaryList;
var presentationModel = this.CreatePresentationModel();
Assert.IsTrue(4 == presentationModel.CurrentStocks.Count);
presentationModel.SubmitCommand.Execute("stock");
Assert.IsTrue(2 == presentationModel.CurrentStocks.Count);
}
[TestMethod]
public void SubmitCommandExecutedShouldFilterTheRecordsAndCosiderCapitalCase()
{
List<PositionSummaryItem> accountSummaryList = new List<PositionSummaryItem>();
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock1" });
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "microsoft" });
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "office" });
accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock2" });
this.accountPositionService.List = accountSummaryList;
var presentationModel = this.CreatePresentationModel();
Assert.IsTrue(4 == presentationModel.CurrentStocks.Count);
presentationModel.SubmitCommand.Execute("STOCK");
Assert.IsTrue(2 == presentationModel.CurrentStocks.Count);
}
private StockDetailsPresentationModel CreatePresentationModel()
{
return new StockDetailsPresentationModel
(this.stocksDetailsView, this.accountPositionService);
}
}
您可以看到,在测试 ViewModel 时,我模拟了应用程序服务以及视图。
结论
只需付出一点额外的努力,就可以轻松创建可测试的应用程序。这在长期内会非常有益,所以从现在开始,当您编写代码时,请花点时间思考您是否在编写可测试的代码。