模拟用于dut方法的lambda表达式

时间:2017-01-26 19:39:08

标签: c# unit-testing lambda

我有一个类似下面的方法,我想进行单元测试:

public string GetReferentie(string prefix)
{
        IRepositoryAsync<ParameterGetal> parameterGetalRepository = _unitOfWork.RepositoryAsync<ParameterGetal>();
        var dateparameterGetal = parameterGetalRepository
            .Query(o => o.parameter=="Datum")
            .Select()
            .Single();
        var ordertellerparametergetal = parameterGetalRepository
                .Query(o => o.parameter == "orderteller")
                .Select()
                .Single();
        DateTime date = DateTime.Parse(dateparameterGetal.parameterwaarde);
        int orderteller=0;
        if (date == DateTime.Today)
        {
            orderteller = int.Parse(ordertellerparametergetal.parameterwaarde); 
        }
        else
        {
            dateparameterGetal.parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today);
            orderteller = 0;
        }
        orderteller++;

        ordertellerparametergetal.parameterwaarde = orderteller.ToString();

        string result = string.Format("{0}{1:yyyyMMdd}.{2:00}",prefix,DateTime.Today,orderteller);

        return result;
}

这里的事情是我使用2个lambda表达式,这使得很难模拟,因为我在stackoverflow中找到的大多数解决方案都会找出一个基于忽略正在使用的lambda的解决方案。

你是怎么做到的? 请注意,我有一个解决方案。我接下来会把它作为答案发布。

1 个答案:

答案 0 :(得分:0)

有时,对于单元测试,必须在DUT的设计中务实,并且可能在仅用于单元测试的类中添加一些东西。 我从上面调整了类,使lambda表达式变量,并允许通过getter访问这些表达式。这样单元测试可以访问getter,因此mocker可以真正检查传递的是哪个lambda。 这里有足够的话题是代码:

public class ReferentieBuilder : IReferentieBuilder
{
    private readonly IUnitOfWorkAsync _unitOfWork;
    private static readonly Expression<Func<ParameterGetal, bool>> _datumExpression = o => o.parameter=="Datum";
    private static readonly Expression<Func<ParameterGetal, bool>> _ordertellerExpression = o => o.parameter == "orderteller";

    public ReferentieBuilder(IUnitOfWorkAsync unitOfWork)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException("unitOfWork");
        }
        _unitOfWork = unitOfWork;
    }

    public static Expression<Func<ParameterGetal, bool>> DatumExpression
    {
        get { return _datumExpression;}
    }

    public static Expression<Func<ParameterGetal, bool>> OrderTellerExpression
    {
        get { return _ordertellerExpression; }
    }

    public string GetReferentie(string prefix)
    {
        IRepositoryAsync<ParameterGetal> parameterGetalRepository = _unitOfWork.RepositoryAsync<ParameterGetal>();
        Debug.Assert(parameterGetalRepository!=null);
        var dateparameterGetal = parameterGetalRepository
            .Query(DatumExpression)
            .Select()
            .Single();
        var ordertellerparametergetal = parameterGetalRepository
                .Query(OrderTellerExpression)
                .Select()
                .Single();
        DateTime date = DateTime.Parse(dateparameterGetal.parameterwaarde);
        int orderteller=0;
        if (date == DateTime.Today)
        {
            orderteller = int.Parse(ordertellerparametergetal.parameterwaarde); 
        }
        else
        {
            dateparameterGetal.parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today);
            orderteller = 0;
        }
        orderteller++;

        ordertellerparametergetal.parameterwaarde = orderteller.ToString();

        string result = string.Format("{0}{1:yyyyMMdd}.{2:00}",prefix,DateTime.Today,orderteller);

        return result;
    }
}

看到我已经拥有了两个静态只读成员变量,并且预见到了静态只读属性。 DatumExpression和OrderTellerExpression。

单位测试代码就像这样:

[TestFixture]
class ReferentieBuilderTests
{
    private IUnitOfWorkAsync _unitOfWork;

    [SetUp]
    public void setup()
    {
        List<ParameterGetal> dateParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 29, parameter = "Datum", parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today.AddDays(-1)) }
        };

        List<ParameterGetal> tellerParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 3, parameter = "orderteller", parameterwaarde = "4" }
        };

        IQueryFluent<ParameterGetal> datefluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IQueryFluent<ParameterGetal> tellerfluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IRepositoryAsync<ParameterGetal> parametergetalrepository = MockRepository.GenerateStub<IRepositoryAsync<ParameterGetal>>();
        _unitOfWork = MockRepository.GenerateStub<IUnitOfWorkAsync>();

        _unitOfWork.Stub(u => u.RepositoryAsync<ParameterGetal>())
            .Return(parametergetalrepository);
        parametergetalrepository.Stub(r => r.Query(ReferentieBuilder.DatumExpression))
            .Return(datefluent);
        parametergetalrepository.Stub(r => r.Query(ReferentieBuilder.OrderTellerExpression))
            .Return(tellerfluent);
        datefluent.Stub(q => q.Select())
            .Return(dateParameterGetallen);
        tellerfluent.Stub(q => q.Select())
            .Return(tellerParameterGetallen);
    }

    [Test]
    public void GetFirstReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.01", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong First Referentie");
    }

    [Test]
    public void GetSecondReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.02", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }

    [Test]
    public void GetThirdReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.03", prefix, today);
        Assert.AreEqual(correctReferentie, referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }
}

这里的诀窍是模拟使用ReferentieBuilder.DatumExpression和ReferentieBuilder.OrderTellerExpression,而不是在单元测试中以硬编码方式传入相同的lambda表达式。 这具有额外的优点,即没有重复。 通过为允许单元测试访问的lambda表达式添加readonly属性,代码并没有那么复杂。

请不要过多地判断代码的逻辑。它正在尝试生成一个每隔一天从1开始的运行编号。访问该函数的最后时间存储在数据库中,也存储在最后一个数字中。这是按照要求,我也不喜欢它。

也可以使用Eugene评论解决方案:

[TestFixture]
class ReferentieBuilderTests
{
    private IUnitOfWorkAsync _unitOfWork;

    [SetUp]
    public void setup()
    {
        List<ParameterGetal> dateParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 29, parameter = "Datum", parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today.AddDays(-1)) }
        };

        List<ParameterGetal> tellerParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 3, parameter = "orderteller", parameterwaarde = "4" }
        };

        IQueryFluent<ParameterGetal> datefluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IQueryFluent<ParameterGetal> tellerfluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IRepositoryAsync<ParameterGetal> parametergetalrepository = MockRepository.GenerateStub<IRepositoryAsync<ParameterGetal>>();
        _unitOfWork = MockRepository.GenerateStub<IUnitOfWorkAsync>();

        _unitOfWork.Stub(u => u.RepositoryAsync<ParameterGetal>())
            .Return(parametergetalrepository);
        parametergetalrepository.Stub(r => r.Query(Arg<Expression<Func<ParameterGetal, bool>>>.Matches(a => LambdaCompare.Eq(a, o => o.parameter == "Datum"))))
            .Return(datefluent);
        parametergetalrepository.Stub(r => r.Query(Arg<Expression<Func<ParameterGetal, bool>>>.Matches(a => LambdaCompare.Eq(a, o => o.parameter == "orderteller"))))
            .Return(tellerfluent);
        datefluent.Stub(q => q.Select())
            .Return(dateParameterGetallen);
        tellerfluent.Stub(q => q.Select())
            .Return(tellerParameterGetallen);
    }

    [Test]
    public void GetFirstReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.01", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong First Referentie");
    }

    [Test]
    public void GetSecondReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.02", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }

    [Test]
    public void GetThirdReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.03", prefix, today);
        Assert.AreEqual(correctReferentie, referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }
}

lambda表达式比较器可以在这里找到: Most efficient way to test equality of lambda expressions

使用链接的更新。 使用此解决方案,不需要对DUT进行更新,即不需要表达式只读属性。