【问题标题】:Unit testing the Viewmodel对 Viewmodel 进行单元测试
【发布时间】:2011-01-30 20:32:15
【问题描述】:

我对 TDD 有点陌生。我已经开始在视图模型上创建我需要的属性作为普通的自动属性。

public string Firstname { get; set; }

然后我创建一个测试

[TestMethod]
[Tag("Property")]
public void FirstNameTest()
{
    ViewModel = new CustomerViewModel();
    ViewModel.PropertyChanged += (s, e) =>
                                     {
                                         Assert.AreEqual("Firstname", e.PropertyName);
                                         Assert.AreEqual("Test", ViewModel.Firstname);
                                     };
    ViewModel.Firstname = "Test";
}

然后我将扩展实际实现以使测试通过,如下所示:

public string Firstname
{
    get { return _contact.FirstName; }
    set
    {
        if (_contact.FirstName == value)
            return;

        _contact.FirstName = value;

        RaisePropertyChanged(() => Firstname);
    }
}

我遇到的问题是 Aut 属性的测试仍然通过。对我有什么建议可以改进我的流程吗?

【问题讨论】:

  • 您不应该将断言放在 lambda 中。断言失败时抛出异常。如果您在 lambdas 中执行此操作,那么它们将在被测对象内部触发,并且您冒着被对象处理的风险。相反,您应该将结果分配给测试范围内的一些(通常是 bool)变量,然后在您返回并展开调用堆栈时对这些变量进行断言。

标签: c# .net silverlight unit-testing tdd


【解决方案1】:

你可以这样做:

    [TestMethod]
    [Tag("Property")]
    public void FirstNameTest()
    {
        bool didFire = false;
        ViewModel = new CustomerViewModel();
        ViewModel.PropertyChanged += (s, e) =>
                                         {
                                             didFire = true;
                                             Assert.AreEqual("Firstname", e.PropertyName);
                                             Assert.AreEqual("Test", ViewModel.Firstname);
                                         };
        ViewModel.Firstname = "Test";
        Assert.IsTrue(didFire);
    }

【讨论】:

  • 非常感谢您的明确回答。现在可以了。但是,Assert 在最后一行抛出 AssertFailedException。我必须按 F5 才能继续,然后我在结果中看到测试失败。我正在使用 VS 2010 附带的 Silverlight 4 单元测试工具包。这很烦人,是否可以抑制它,如果出现问题,我不想稍后在 50 个单元测试上按 F5。 :)
  • 不要在调试模式下运行测试:)
  • 我在 Release 下运行它,但它仍然出现中断。它非常烦人。我在 Silverlight 4 测试框架下使用单元测试。有人知道吗?
  • 我找到了。它与调试模式无关,答案在这里:blog.benday.com/archive/2010/08/18/23287.aspx
  • @Antoine Aubry,或者他可以点击继续按钮或按 F5。这是相同的行为。
【解决方案2】:

您可以尝试将测试编写为异步的。考虑这种测试方法:

[TestMethod]
[Asynchronous]
public void TestMethod1()
{
    TestViewModel testViewModel = new TestViewModel();

    bool firstNameChanged = false;

    testViewModel.PropertyChanged +=
        (s, e) =>
            {
                if (e.PropertyName == "FirstName")
                {
                    firstNameChanged = true;
                }
            };

    EnqueueCallback(() => testViewModel.FirstName = "first name");
    EnqueueConditional(() => firstNameChanged == true);
    EnqueueTestComplete();
}

注意方法顶部的异步属性。这里有两个重要的方法:EnqueueCallback 和 EnqueueTestComplete。 EnqueueCallback 会将 lambda 表达式添加到队列中,并且测试方法将等待直到执行当前回调。在本例中,我们订阅 ViewModel 上的 PropertyChanged 事件,并在 FirstName 属性通知更改时将本地布尔变量设置为 true。然后我们将两个回调入队:一个用于设置 FirstName 属性,另一个用于断言本地布尔变量已更改值。最后,我们需要添加对 EnqueueTestComplete() 的调用,以便框架知道测试已经结束。

注意:为了获得 EnqueueCallback 和 EnqueueTestComplete,您需要从测试类上的 SilverlightTest 继承。您还需要导入 Microsoft.Silverlight.Testing 以获取 Asynchronous 属性。它应该看起来像这样:

using Microsoft.Silverlight.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Foo.Example.Test
{
    [TestClass]
    public class Tests : SilverlightTest
    {

        // ... tests go here
    }
}

【讨论】:

  • 非常感谢。这个答案在 Silverlight 级别上帮助了我更多。但是我想评论一件事,如果您使用 EnqueueCallback 而没有任何 EnqueueConditional 情况,则与您无论如何都要使调用同步一样。
  • 是的,EnqueueConditional 应该用于使测试真正等待事件触发。我想我的例子有点太琐碎了,我很抱歉。它现在使用 EnqueueConditional 等待 firstNamedChanged 的​​布尔状态变为 true。
【解决方案3】:

您需要进行另一个测试来实际断言您的 PropertyChanged 甚至会触发。

当您进行该测试时,您的 auto 属性应该会失败,因为该事件永远不会触发。

Here's an example of how to do that in Moq

【讨论】:

  • 非常感谢这篇文章,我会尽快研究它,看看我还能从中学到什么。
  • 不幸的是,链接已失效,并且没有从文章中复制任何信息。
【解决方案4】:

除非它正在测试的行为已经实现,否则测试应该失败。

为了在我上次尝试中测试属性更改通知,我创建了a helper class,它帮助我编写了这样的测试(它在 NUnit 中)

[Test]
public void NotifiesChangeIn_TogglePauseTooltip()
{
   var listener = new PropertyChangeListener(_mainViewModel);

   _mainViewModel.TogglePauseCommand.Execute(null);

   Assert.That(listener.HasReceivedChangeNotificationFor("TogglePauseTooltip"));
}

【讨论】:

    【解决方案5】:

    这是我过去的做法(我使用过 NUnit,所以可能有点不同):

    [Test]
    public void ShouldNotifyListenersWhenFirstNameChanges()
    {
        var propertiesChanged = new List<string>();
    
        ViewModel = new CustomerViewModel();
        ViewModel.PropertyChanged += (s, e) => propertiesChanged.Add(e.PropertyName);
    
        ViewModel.Firstname = "Test";
    
        Assert.Contains("Firstname", propertiesChanged);      
        Assert.AreEqual("Test", ViewModel.Firstname);  
    }
    

    如果不是Firstname,它还有一个很好的副作用是能够调试和计算出发生了什么变化。当您从其他字段计算多个字段时非常方便。您还可以查看代码中行为的其他方面:

    [Test]
    public void ShouldNotNotifyListenersWhenPropertiesAreNotChanged()
    {
        var propertiesChanged = new List<string>();
    
        ViewModel = new CustomerViewModel();
        ViewModel.Firstname = "Test";
    
        ViewModel.PropertyChanged += (s, e) => propertiesChanged.Add(e.PropertyName);
    
        ViewModel.Firstname = "Test";
    
        Assert.AreEqual(0, propertiesChanged.Count); 
    }
    

    【讨论】:

      【解决方案6】:

      也许这段代码有更多的背景信息没有被披露,但我所看到的似乎不必要地复杂。何必纠结于RaisePropertyChanged 事件呢?设置好后检查属性即可。

      [TestMethod]
      [Tag("Property")]
      public void FirstNameTest()
      {
          var expected = "John";
          var sut = new CustomerViewModel();
      
          sut.Firstname = expected;
      
          Assert.AreEqual(expected, sut.Firstname);
      }
      

      这也将测试变成了真正的单元测试。

      【讨论】:

      • 这有点没用。为什么框架能够正确处理自动属性的设置器?当您想要检查PropertyChanged 事件是否按预期引发时,测试就开始有意义了。 是在这样的测试中唯一值得检查的东西。
      • @Martin 我同意测试自动属性的设置器不会带来任何好处。但是,如果您查看原始代码(由 Hooman 发布),您会注意到该属性仅在传入值不等于 _contact.FirstName 中的值时设置。这就是我的重构测试所关注的代码部分。
      猜你喜欢
      • 2019-12-17
      • 1970-01-01
      • 2023-04-09
      • 2021-08-09
      • 2023-03-29
      • 2013-03-15
      • 2012-01-08
      • 2020-10-17
      • 2012-05-14
      相关资源
      最近更新 更多