单元测试富域模型 [英] Unit Testing Rich Domain Model

查看:52
本文介绍了单元测试富域模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是贫血域模型:

public partial class Person
{
    public virtual int PersonId { get; internal protected set; }
    public virtual string Title { get; internal protected set; } 
    public virtual string FirstName { get; internal protected set; } 
    public virtual string MiddleName { get; internal protected set; } 
    public virtual string LastName { get; internal protected set; } 
}

这是它的行为:

public static class Services
{

    public static void UpdatePerson(Person p, string firstName, string lastName)
    {
        // validate  firstname and lastname
        // if there's a curse word, throw an exception


        // if valid, continue

        p.FirstName = firstName;
        p.LastName = lastName;


        p.ModifiedDate = DateTime.Now;
    }

}

而且它几乎可以测试:

[TestMethod]

public void Is_Person_ModifiedDate_If_Updated()
{
    // Arrange
    var p = new Mock<Person>();

    // Act 
    Services.UpdatePerson(p.Object, "John", "Lennon");

    // Assert            
    p.VerifySet(x => x.ModifiedDate = It.IsAny<DateTime>());
}

然而,我想练习富域模型,其中数据和行为在逻辑上更具凝聚力.所以上面的代码现在转换为:

However, I wanted to practice Rich Domain Model, where data and behavior is more logically-cohesive. So the code above is now converted to:

public partial class Person
{
    public virtual int PersonId { get; internal protected set; }
    public virtual string Title { get; internal protected set; }
    public virtual string FirstName { get; internal protected set; } 
    public virtual string MiddleName { get; internal protected set; }
    public virtual string LastName { get; internal protected set; } 

    public virtual void UpdatePerson(string firstName, string lastName)
    {
        // validate  firstname and lastname
        // if there's a curse word, throw an exception


        // if valid, continue


        this.FirstName = firstName;
        this.LastName = lastName;

        this.ModifiedDate = DateTime.Now;
    }           
}

但是我遇到了测试问题:

However I encounter testing problem:

[TestMethod]
public void Is_Person_ModifiedDate_If_Updated()
{
    // Arrange
    var p = new Mock<Person>();

    // Act 
    p.Object.UpdatePerson("John", "Lennon");

    // Assert            
    p.VerifySet(x => x.ModifiedDate = It.IsAny<DateTime>());
}

单元测试错误:

Result Message: 

Test method Is_Person_ModifiedDate_If_Updated threw exception: 
Moq.MockException: 
Expected invocation on the mock at least once, but was never performed: x => x.ModifiedDate = It.IsAny<DateTime>()
No setups configured.

Performed invocations:
Person.UpdatePerson("John", "Lennon")
Result StackTrace:  
at Moq.Mock.ThrowVerifyException(MethodCall expected, IEnumerable`1 setups, IEnumerable`1 actualCalls, Expression expression, Times times, Int32 callCount)
   at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
   at Moq.Mock.VerifySet[T](Mock`1 mock, Action`1 setterExpression, Times times, String failMessage)
   at Moq.Mock`1.VerifySet(Action`1 setterExpression)
   at Is_Person_ModifiedDate_If_Updated()

看到直接从被模拟的对象调用方法,被模拟的对象无法检测是否调用了它的任何属性或方法.注意到这一点后,对富域模型进行单元测试的正确方法是什么?

Seeing that directly invoking a method from the mocked's Object, the mocked object then can't detect if any of its property or method was called. Having noticed that, what's the proper way to unit test a Rich Domain Model?

推荐答案

首先,不要模拟您正在测试的值对象或类.您也没有验证是否向人员提供了正确的修改日期.您检查是否分配了某个日期.但这并不能证明您的代码按预期工作.为了测试此类代码,您应该模拟当前日期 DateTime 返回.Now,或创建一些抽象,这将提供当前的服务时间.您的第一个测试应该看起来像(我在这里使用了 Fluent Assertions 和 NUnit):

First, don't mock value objects or classes you are testing. Also you are not verifying that correct modification date was provided to person. You check that some date was assigned. But that does not prove your code works as expected. In order to tests such code you should mock current date returned by DateTime.Now, or create some abstraction, which will provide current time to service. Your first test should look like (I used Fluent Assertions and NUnit here):

[Test]
public void Should_Update_Person_When_Name_Is_Correct()
{
    // Arrange
    var p = new Person(); // person is a real class
    var timeProviderMock = new Mock<ITimeProvider>();
    var time = DateTime.Now;
    timeProviderMock.Setup(tp => tp.GetCurrentTime()).Returns(time);
    Services.TimeProvider = timeProviderMock.Object;
    // Act 
    Services.UpdatePerson(p, "John", "Lennon");
    // Assert
    p.FirstName.Should().Be("John");
    p.LastName.Should().Be("Lennon");
    p.ModifiedDate.Should().Be(time); // verify that correct date was set
    timeProviderMock.VerifyAll();
}

时间提供者是一个简单的抽象:

Time provider is a simple abstraction:

public interface ITimeProvider
{
    DateTime GetCurrentTime();
}

我会使用单例服务而不是静态类,因为静态类总是有问题——高耦合、没有抽象、难以对依赖类进行单元测试.但是你可以通过属性注入时间提供者:

I'd go with singleton service instead of static class, because static classes are always problem - high coupling, no abstraction, hard to unit-test dependent classes. But you can inject time provider via property:

public static class Services
{
    public static ITimeProvider TimeProvider { get; set; }

    public static void UpdatePerson(Person p, string firstName, string lastName)
    {
        p.FirstName = firstName;
        p.LastName = lastName;
        p.ModifiedDate = TimeProvider.GetCurrentTime();
    }
}

同样与您的第二次测试有关.不要模拟您正在测试的对象.您应该验证您的应用程序将使用的真实代码,而不是测试一些仅由测试使用的模拟.使用覆盖域模型进行测试将如下所示:

Same relates to your second test. Do not mock object you are testing. You should verify real code, which your application will use, instead of testing some mock, which is used only by test. Test with reach domain model will look like:

[Test]
public void Should_Update_Person_When_Name_Is_Correct()
{
    // Arrange        
    var timeProviderMock = new Mock<ITimeProvider>();
    var time = DateTime.Now;
    timeProviderMock.Setup(tp => tp.GetCurrentTime()).Returns(time);
    var p = new Person(timeProviderMock.Object); // person is a real class
    // Act 
    p.Update("John", "Lennon");
    // Assert
    p.FirstName.Should().Be("John");
    p.LastName.Should().Be("Lennon");
    p.ModifiedDate.Should().Be(time); // verify that correct date was set
    timeProviderMock.VerifyAll();
}

这篇关于单元测试富域模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆