65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2010年5月9日

CPOL

5分钟阅读

viewsIcon

28412

downloadIcon

171

本文介绍了编写高度可进行单元测试的 Silverlight 应用程序的最佳实践。

引言

单元测试用于验证函数或函数集是否“遵守其契约”——换句话说,就是被测试的函数(或函数集)是否满足要求。单元测试会检查黑盒和白盒。

本文不讨论什么是单元测试或如何编写单元测试用例。网上有很多相关的文章。 这里有一个很好的链接,您可以从中获取有关单元测试的信息。

开发者为了实现功能而直接在 XAML 代码隐藏文件中编写代码是很常见的,但这会降低应用程序的可测试性。本文将介绍如何编写高度可测试的代码。

背景

UnitTestDemoApp

我从 PRISM 框架的 Stock Trader 示例应用程序中获得了这个应用程序的灵感。该应用程序有两个网格。第一个网格列出了所有可用的股票。用户可以选择一只股票并将其添加到观察列表中。第二个网格包含用户想要观察的股票。用户可以从观察列表中选择一只股票,然后单击“移除”将其从观察列表中删除。用户可以在搜索文本框中输入文本并单击“搜索”按钮来搜索股票。

这里我们不关注应用程序及其 UI 方面。我们只关注应用程序的单元测试。

Using the Code

随附的 zip 文件包含两个 Visual Studio 解决方案。

  1. UnitTestDemoApp\UnitTestDemo\UnitTestDemo.sln:这是理想的测试解决方案。XAML 代码隐藏中几乎没有代码。
  2. UnitTestDemoStart\UnitTestDemo\UnitTestDemo.sln:这是非常糟糕的测试解决方案。几乎所有的代码都在代码隐藏中。此解决方案无法进行单元测试。

两个解决方案都包含以下项目

  1. UnitTestDemo:Silverlight 应用程序
  2. UnitTestDemo.Web:托管 Silverlight 应用程序的 Web 应用程序
  3. UnitTestDemoTests:Silverlight 应用程序的测试项目

以下是解决方案中使用的不同类

  1. PositionSummaryItem:包含股票的详细信息。
  2. AccountPositionService:服务类,用于从 XML 文件检索股票详细信息。
  3. CurrencyConverter:用于将值转换为美元格式的转换器。Silverlight 4.0 支持此功能。我们可以在 Silverlight 4.0 中指定格式。
  4. DecimalToColorConverter:如果值为负数或正数,则用红色或绿色背景色显示值的转换器。
  5. 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 中找到不同模式的定义

以下是 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 时,我模拟了应用程序服务以及视图。

结论

只需付出一点额外的努力,就可以轻松创建可测试的应用程序。这在长期内会非常有益,所以从现在开始,当您编写代码时,请花点时间思考您是否在编写可测试的代码。

© . All rights reserved.