F#:为什么这两个集合不相等? [英] F#: Why those two collections are not equal?

查看:86
本文介绍了F#:为什么这两个集合不相等?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用XUnit编写某些单元之前,直到某些时候我碰到了一些令人惊讶的事情:

Was writing some units using XUnit until that at some points I bumped into something surprising:

let id = Guid.Empty
let contact = {
    Name = {
        FirstName = "Marcel"
        MiddleInitial = None
        LastName = "Patulacci"
    }
    DateOfBith = new DateTime(1850, 12, 25)
    Address = {
        Address1 = "41 av 8 Mai 1945"
        Address2 = None
        City = "Sarcelles"
        State = None
        Zip = "95200"
    }
    PhoneNumber = {
        DialOutCode = 33
        LocalNumber = "766030703"
    }
    Email = "marcel.patulacci@outlook.com"
}

[<Fact>]
let ``Open an account...``() =
    let event = Event.AccountOpened({
        AccountId = id
        Contact = contact
    })

    let a = [event]
    let b = seq { yield event }

    Assert.Equal(a, b)

System.NullReferenceException:对象引用未设置为对象的实例.

System.NullReferenceException : Object reference not set to an instance of an object.

尤其令人惊奇的是,考虑到Assert使用的重载为:

It seems surprising especially since considering that the overload used by Assert is:

public static void Equal<T>(IEnumerable<T> expected, IEnumerable<T> actual)

其中指出:

使用默认比较器验证两个序列是否相等.

Verifies that two sequences are equivalent, using a default comparer.

为什么认为它们不同,为什么Assert.Equal会引起System.NullReferenceException?

Why are they considered different, and why does Assert.Equal raise a System.NullReferenceException?

System.NullReferenceException:未将对象引用设置为对象的实例. 在Domain.Events.AccountOpenedEvent.Equals(Object obj, IEqualityComparer comp)Domain.Events.Event.Equals(Object obj, IEqualityComparer comp)

System.NullReferenceException : Object reference not set to an instance of an object. at Domain.Events.AccountOpenedEvent.Equals(Object obj, IEqualityComparer comp) at Domain.Events.Event.Equals(Object obj, IEqualityComparer comp)

似乎


type PersonalName = {
    FirstName: string;
    MiddleInitial: string option;
    LastName: string;
}

type Address = {
    Address1: string;
    Address2: string option ;
    City: string;
    State: string option;
    Zip: string;
}

type PhoneNumber = {
    DialOutCode : int;
    LocalNumber: string
}

type Contact = {
    Name: PersonalName;
    DateOfBith: DateTime
    Email: string;
    Address: Address;
    PhoneNumber: PhoneNumber
}

type AccountOpenedEvent = {
    AccountId: Guid
    Contact: Contact
}

type Event =
    | AccountOpened of AccountOpenedEvent

事实证明,event的字段之一是null,而不是event本身.

It turns out one of the fields of event was null, but not event itself.

推荐答案

问题出在测试/[<Fact>]上方定义的idcontact中:

The problem resided in the id and contact that were defined right above the test / [<Fact>]:

let id = Guid.Empty
let contact = {
    Name = {
        FirstName = "Marcel"
        MiddleInitial = None
        LastName = "Patulacci"
    }
    DateOfBith = new DateTime(1850, 12, 25)
    Address = {
        Address1 = "41 av 8 Mai 1945"
        Address2 = None
        City = "Sarcelles"
        State = None
        Zip = "95200"
    }
    PhoneNumber = {
        DialOutCode = 33
        LocalNumber = "766030703"
    }
    Email = "marcel.patulacci@outlook.com"
}

[<Fact>]
let ``Open an account...``() =
    let event = Event.AccountOpened({
        AccountId = id
        Contact = contact
    })

    let a = [event]
    let b = seq { yield event }

    Assert.Equal(a, b)

问题是,当独立运行测试时,idcontact未初始化,因此,即使event不是null,接触也是null(idGuid一个struct它仍然具有值).

The thing is when running the test independently the id and contact are not initialized, hence even though event was not null, contact was null (id being a Guid aka a struct it has a value anyway).

由于F#具有结构相等性,因此如果未初始化该字段之一,则足以具有一个null字段,以使Assert在其实现的某个时刻失败.

Since F# works with structural equality, if one of the field is not initialized it was enough to have a field null to make the Assert failed at some point in its implementation.

有一些解决方案/解决方法:

There are a few solutions / workarounds:

  1. 直接在单元测试主体中定义这些变量.
  2. 从单元测试主体中产生这些值的定义方法

let getId() = Guid.Empty
let getContact() = {
    Name = {
        FirstName = "Marcel"
        MiddleInitial = None
        LastName = "Patulacci"
    }
    DateOfBith = new DateTime(1850, 12, 25)
    Address = {
        Address1 = "41 av 8 Mai 1945"
        Address2 = None
        City = "Sarcelles"
        State = None
        Zip = "95200"
    }
    PhoneNumber = {
        DialOutCode = 33
        LocalNumber = "766030703"
    }
    Email = "marcel.patulacci@outlook.com"
}

[<Fact>]
let ``Open an account...``() =
    let id = getId()
    let contact = getContact()

    let event = Event.AccountOpened({
        AccountId = id
        Contact = contact
    })

    let a = [event]
    let b = seq { yield event }

    Assert.Equal(a, b)

尽管这些变通办法起作用,但令我惊讶的是,在运行测试/时未考虑在单元测试函数正上方声明的变量,并且这些变量未初始化.

While those workarounds work, I am surprised that the variables declared right above the unit test function are not considered when the test is running / and are uninitialized.

也许值得再问一个问题,为什么会这样. 从某种意义上说,这是令人惊讶的,如果可以定义一个函数并返回与那些变量几乎相同的东西,则意味着let也已正确编译,那么为什么变量不是这种情况?

It might worth to shoot another question about why this is the case. This is surprising in the sense that if a function can be defined and returning pretty much the same thing as those variables it means that let is also properly compiled, so why this is not the case with the variables?

这篇关于F#:为什么这两个集合不相等?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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