简体   繁体   English

在dut方法中使用的模拟lambda表达式

[英]mocking lambda expressions that are used in methods of the dut

I have a method like below that I want to unit test: 我有一个下面要进行单元测试的方法:

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;
}

The thing here is that i am using 2 lambda expressions and that makes it difficult to mock since most solutions i found in stackoverflow work out a solution that is based on ignoring the lambda that is being used. 这里的问题是,我正在使用2个lambda表达式,这使模拟变得很困难,因为我在stackoverflow中找到的大多数解决方案都制定了基于忽略正在使用的lambda的解决方案。

How do you go about this ? 您如何处理? Note, i have a solution. 注意,我有解决方案。 I will post it as an answer next. 接下来,我将其发布为答案。

Sometimes for unit testing one has to be pragmatic in the design of the DUT and perhaps add something to the class that is only used in the unit test. 有时对于单元测试,必须在DUT的设计中务实,并且可能在类中添加仅用于单元测试的内容。 I adapted the class from above to make the lambda expressions variabled and allow access to those expressions via a getter. 我从上面修改了该类,以使lambda表达式可变,并允许通过getter访问这些表达式。 This way the unit test can access the getter, and hence the mocker can really check which lambda is being passed. 这样,单元测试可以访问getter,因此模拟程序可以真正检查要传递的lambda。 Enough talk here is the code: 这里有足够的谈话是代码:

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;
    }
}

see I hadded two static readonly member variables and foreseen static readonly properties as well. 请参见我已经伪装了两个静态只读成员变量,并预见了静态只读属性。 DatumExpression and OrderTellerExpression. DatumExpression和OrderTellerExpression。

The unit test code becomes then something like this : 单元测试代码将变成这样:

[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");
    }
}

The trick here is that the mocks is using ReferentieBuilder.DatumExpression and ReferentieBuilder.OrderTellerExpression and not passing in the same lambda expression in a hardcoded way in the unit test. 这里的窍门是,该模拟使用的是ReferentieBuilder.DatumExpression和ReferentieBuilder.OrderTellerExpression,而不是在单元测试中以硬编码方式传递相同的lambda表达式。 This has an extra advantage that there is no duplication. 这具有不重复的额外优点。 The code is not that much extra complicated by adding the readonly properties for the lambda expressions that allow the unit test to access. 通过为允许单元测试访问的lambda表达式添加只读属性,代码并没有那么复杂。

Please don't judge the logic of the code too much. 请不要过多地判断代码的逻辑。 It is trying to generate a running number that starts at 1 every new day. 它正在尝试生成一个从每天1开始的运行编号。 The last time the function was accessed is stored in the database, and the last number as well. 上次访问该函数的时间以及最后一个数字也存储在数据库中。 This is as per requirement and i don't like it either. 这是根据要求,我也不喜欢。

Using Eugene comment following solution is also possible : 也可以使用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");
    }
}

the lambda expression comparer can be found here : Most efficient way to test equality of lambda expressions 可以在这里找到lambda表达式比较器: 测试lambda表达式相等性的最有效方法

use the update of the link. 使用链接的更新。 Using this solution, no updates on the DUT are needed, ie no expression readonly properties are required. 使用此解决方案,无需在DUT上进行任何更新,即,无需表达式只读属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM