我一直在使用通常由构建器模式组成的反射来构建我的域对象,以便为我的域层编写单元测试(使用 xUnit)。正如我在较新版本的 C# 和 .net 中看到的那样,有一种方法可以使用不可变的记录。我的问题是,使用记录而不是反射构建器从域类实例化的那些对象是一种好方法吗?
这是我的反射生成器类,我将其用作抽象类:
using System.Linq.Expressions;
using System.Reflection;
namespace Utility.ReflectionTools
{
public abstract class ReflectionBuilder<TReflection, TBuilder>
where TReflection : class
where TBuilder : ReflectionBuilder<TReflection, TBuilder>
{
private readonly TBuilder? _builderInstance = null;
public ReflectionBuilder()
{
//store the concrete builder instance
_builderInstance = (TBuilder)this;
}
private class PropertyWrapper
{
public PropertyInfo Property { get; }
public object Value { get; set; }
internal PropertyWrapper(PropertyInfo property, object value)
{
Property = property;
Value = value;
}
}
public TBuilder With<TValue>(Expression<Func<TReflection, TValue>> exp, TValue value)
{
var body = exp.Body as MemberExpression;
if (body == null)
{
throw new InvalidOperationException("Improperly formatted expression");
}
var propertyName = body.Member.Name;
var property = GetType().GetRuntimeField(propertyName);
if (property != null)
{
property.SetValue(_builderInstance, value);
}
else
{
GetType().GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(_builderInstance, value);
}
return _builderInstance!;
}
public abstract TReflection Build();
}
}
这是我用于构建域类实例的测试构建器示例:
using Domain.Customers;
using DomainContract.Constants.Customers;
using Utility.ReflectionTools;
namespace DomainTest.Customers.TestBuilders
{
public class CustomerDomainTestBuilder:ReflectionBuilder<CustomerDomain, CustomerDomainTestBuilder >
{
private CustomerDomainTestBuilder _builderInstance;
public string FirstName = CustomerConstants.SomeFirstName;
public string LastName = CustomerConstants.SomeLastName;
public DateTimeOffset DateOfBirth = CustomerConstants.SomeDateOfBirth;
public string PhoneNumber = CustomerConstants.SomePhoneNumber;
public string Email = CustomerConstants.SomeEmail;
public string BankAccountNumber = CustomerConstants.SomeBankAccountNumber;
public CustomerDomainTestBuilder()
{
_builderInstance = this;
}
public override CustomerDomain Build()
{
var customerDomain = new CustomerDomain(this.FirstName, this.LastName, this.DateOfBirth, this.PhoneNumber,
this.Email, this.BankAccountNumber);
return customerDomain;
}
}
}
这是我的领域类:
using Domain.Abstractions;
namespace Domain.Customers
{
public class CustomerDomain : AggregateRoot
{
public string FirstName { get; private set; } = null!;
public string LastName { get; private set; } = null!;
public DateTimeOffset DateOfBirth { get; private set; }
public string PhoneNumber { get; private set; } = null!;
public string Email { get; private set; } = null!;
public BankAccountVo BankAccount { get; private set; } = null!;
public CustomerDomain(string firstName, string lastName, DateTimeOffset dateOfBirth, string phoneNumber, string email, string bankAccountNumber)
{
if (string.IsNullOrWhiteSpace(firstName))
{
throw new Exception("First name is null,empty or white space.") { HResult = 10 };
}
if (string.IsNullOrWhiteSpace(lastName))
{
throw new Exception("Last name is null,empty or white space.") { HResult = 20 };
}
if (DateTimeOffset.Now.Year - DateOfBirth.Year < 12)
{
throw new Exception("Customer should be at least 12 years old.") { HResult = 30 };
}
if (string.IsNullOrWhiteSpace(phoneNumber))
{
throw new Exception("PhoneNumber is null, empty or white space.") { HResult = 40 };
}
if (string.IsNullOrWhiteSpace(email))
{
throw new Exception("Email is null, empty or white space.") { HResult = 50 };
}
if (string.IsNullOrWhiteSpace(bankAccountNumber))
{
throw new Exception("BankAccountNumber is null, empty or white space.") { HResult = 60 };
}
FirstName = firstName;
LastName = lastName;
DateOfBirth = dateOfBirth;
PhoneNumber = phoneNumber;
Email = email;
BankAccount = new BankAccountVo(bankAccountNumber);
}
}
}
最后,这是我提到的域类的测试类:
using Domain.Customers;
using DomainTest.Customers.TestBuilders;
using FluentAssertions;
namespace DomainTest.Customers
{
//Todo: avoid using hard code messages for exceptions. try using constants.
public class CustomerTest
{
[Fact]
public void FirstName_Should_Not_Be_NullOrWhiteSpace()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.FirstName, string.Empty)
.Build();
};
act.Should().Throw<Exception>().WithMessage("First name is null,empty or white space.");
}
[Fact]
public void LastName_Should_Not_Be_NullOrWhiteSpace()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.LastName, string.Empty)
.Build();
};
act.Should().Throw<Exception>().WithMessage("Last name is null, empty or white space.");
}
[Fact]
public void Customer_Should_Be_Equal_Or_Greater_Than_Twelve_years_Old()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.DateOfBirth, new DateTimeOffset(new DateTime(2011, 12, 25), new TimeSpan(0, 3, 30, 0)))
.Build();
};
act.Should().Throw<Exception>().WithMessage("Customer should be at least 12 years old.");
}
[Fact]
public void PhoneNumber_Should_Not_Be_NullOrWhiteSpace()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.PhoneNumber, string.Empty)
.Build();
};
act.Should().Throw<Exception>().WithMessage("PhoneNumber is null, empty or white space.");
}
[Fact]
public void Email_Should_Not_Be_NullOrWhiteSpace()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.Email, string.Empty)
.Build();
};
act.Should().Throw<Exception>().WithMessage("Email is null, empty or white space.");
}
[Fact]
public void BankAccountNumber_Should_Not_Be_NullOrWhiteSpace()
{
Action act = () =>
{
var customer = new CustomerDomainTestBuilder()
.With(x => x.BankAccount, new BankAccountVo(string.Empty))
.Build();
};
act.Should().Throw<Exception>().WithMessage("BankAccountNumber is null, empty or white space.");
}
//Todo: due to lck of time, we did cancel adding domain contract and adding domain service of validation. it should be like that.
}
}
我没有尝试记录,因为我对它的概念有点困惑。任何人都可以帮助我使用记录在测试中构建我的域对象吗?