需要帮助在MVVMLight中使用Messaging编写单元测试用例

时间:2013-06-27 11:05:34

标签: unit-testing mvvm-light static-methods testcase

我对单元测试用例写作完全陌生。我在WPF中使用MVVMLigh。是否有必要使用一些第三方测试框架或.Net Unit测试框架呢?另外如何在单元测试用例中处理静态类?在本例中为AppMessages类。

有人可以指导我如何为下面的代码编写单位案例:

public MyViewModel(Participant participant)
{    
    if (participant != null)
    {
        this.ParentModel = parentModel;
        OkCommand = new RelayCommand(() => OkCommandExecute());
        CalculateAmountCommand = new RelayCommand(() => CalculateAmount());        
    }
    else
    {
        ExceptionLogger.Instance.LogException(Constants.ErrorMessages.FinancialLineCanNotBeNull, "FinancialLineViewModel");
        AppMessages.DisplayDialogMessage.Send(Constants.ErrorMessages.FinancialLineCanNotBeNull, MessageBoxButton.OK, Constants.DefaultCaption, null);
    }
}

public static class AppMessages
{
    enum AppMessageTypes
    {
        FinancialLineViewDisplay,
        FinancialLineViewClose,
        DisplayDialogMessage
    }

    public static class DisplayDialogMessage
    {
        public static void Send(string message, MessageBoxButton button, string caption, System.Action<MessageBoxResult> action)
        {
            DialogMessage dialogMessage = new DialogMessage(message, action)
            {
                Button = button,
                Caption = caption
            };

            Messenger.Default.Send(dialogMessage, AppMessageTypes.DisplayDialogMessage);
        }

        public static void Register(object recipient, System.Action<DialogMessage> action)
        {
            Messenger.Default.Register<DialogMessage>(recipient, AppMessageTypes.DisplayDialogMessage, action);
        }
    }
}

public class ExceptionLogger
{
    private static ExceptionLogger _logger;
    private static object _syncRoot = new object();

    public static ExceptionLogger Instance
    {
        get
        {
            if (_logger == null)
            {
                lock (_syncRoot)
                {
                    if (_logger == null)
                    {
                        _logger = new ExceptionLogger();
                    }
                }
            }

            return _logger;
        }
    }

    public void LogException(Exception exception, string additionalDetails)
    {
        LogException(exception.Message, additionalDetails);
    }

    public void LogException(string exceptionMessage, string additionalDetails)
    {
        MessageBox.Show(exceptionMessage);
    }
}

1 个答案:

答案 0 :(得分:5)

关于可测试性

由于使用单例和静态类,MyViewModel无法测试。单元测试是关于隔离的。如果要对某个类(例如,MyViewModel)进行单元测试,则需要能够通过test double(通常是存根或模拟)替换其依赖项。只有在代码中提供接缝时才会出现此功能。用于提供接缝的最佳技术之一是依赖注入。学习DI的最佳资源来自Mark Seemann (Dependency Injection in .NET)

您无法轻易替换静态成员的调用。因此,如果您使用许多静态成员,那么您的设计并不完美。

当然,您可以使用无约束隔离框架(如Typemock Isolator,JustMock或Microsoft Fakes)伪造静态方法调用,但这需要花钱,并且不会促使您更好地设计。这些框架非常适合为遗留代码创建测试工具。

关于设计

  1. MyViewModel的构造函数做得太多了。 Constructors should be simple
  2. 如果dependecy为null,则构造函数必须抛出ArgumentNullException,但不能以静默方式记录错误。抛出异常清楚地表明您的对象不可用。
  3. 关于测试框架

    您可以使用任何您喜欢的单元测试框架。即使是MSTest,但我个人并不推荐它。 NUnit和xUnit.net要好得多。

    进一步阅读

    1. Mark Seeman - Dependency Injection in .NET
    2. Roy Osherove - The Art of Unit Testing (2nd Edition)
    3. Michael Feathers - Working Effectively with Legacy Code
    4. Gerard Meszaros - xUnit Test Patterns
    5. 示例(使用MvvmLight,NUnit和NSubstitute)

      public class ViewModel : ViewModelBase
      {
          public ViewModel(IMessenger messenger)
          {
              if (messenger == null)
                  throw new ArgumentNullException("messenger");
      
              MessengerInstance = messenger;
          }
      
          public void SendMessage()
          {
              MessengerInstance.Send(Messages.SomeMessage);
          }
      }
      
      public static class Messages
      {
          public static readonly string SomeMessage = "SomeMessage";
      }
      
      public class ViewModelTests
      {
          private static ViewModel CreateViewModel(IMessenger messenger = null)
          {
              return new ViewModel(messenger ?? Substitute.For<IMessenger>());
          }
      
          [Test]
          public void Constructor_WithNullMessenger_ExpectedThrowsArgumentNullException()
          {
              var exception = Assert.Throws<ArgumentNullException>(() => new ViewModel(null));
              Assert.AreEqual("messenger", exception.ParamName);
          }
      
          [Test]
          public void SendMessage_ExpectedSendSomeMessageThroughMessenger()
          {
              // Arrange
              var messengerMock = Substitute.For<IMessenger>();
              var viewModel = CreateViewModel(messengerMock);
      
              // Act
              viewModel.SendMessage();
      
              // Assert
              messengerMock.Received().Send(Messages.SomeMessage);
          }
      }