如何为 LinQ 查询编写 NUnit 测试?

How to write NUnit-Tests for LinQ-Queries?

出于练习目的,我得到了一个很大的 .csv 文件,其中包含 Persons 的多个列。练习是编写多个 LinQ 查询,例如只选择女性等人。例如:

var query = persons.Where(p => p.Salutation == "Ms.");

为了应用查询,我首先将 .csv 文件的每一行转换为一个 class 人物,并将对象添加到包含人物的列表中。

        List<Person> persons = new List<Person>();
        var cr = new CsvableBase.CsvReader<Person>();
        var csvPeople = cr.Read("data.csv", headers = true);

        foreach (var person in csvPeople)
        {
            persons.Add(person);
        }
    }

这按预期工作,我可以通过查看控制台来查询每个查询。例如:

    public static void GetOnlyFemale(List<Person> persons)
    {
        var query = persons.Where(p => p.Salutation == "Ms.");
        foreach (var person in query)
        {
            var properties = person.GetType().GetProperties();
            foreach (var property in properties)
            {
                Console.Write($"{property.Name}: {property.GetValue(entry)}" + ",");
            }

            Console.WriteLine();
        }
    }

现在,下一个练习是为每个查询编写单元测试,但我不知道如何解决这个问题。我想创建一些显示正确结果的行,并将它们与相同数量的查询行进行比较。但是一定有更好的方法吗?

您在编写单元测试时遇到问题的原因是您没有将您的关注点分开:您的 class 可以做的太多了。制作只有一项任务的更小的 classes。如果您不熟悉关注点分离,请考虑阅读有关它的一些背景信息。

将您的 classes 分成一个 class 代表您的存储机制,在您当前的版本中是一个 CSV 文件,以及一个 class 对您的存储进行查询机制.

在你的存储机制的界面中隐藏它是一个CSV文件。这样,将来您可以将存储格式更改为 JSON 文件,或 XML,数据库,或者将来您可以从互联网上获取数据。事实上,对于您的查询而言,数据存储在何处以及如何存储并不重要。您真正想知道的是,您可以为此检索相似对象的可枚举序列。

在你的例子中,你的存储包含一系列人物。所以你的存储至少应该有这样的界面:

Interface IMyDataFetcher
{
    IEnumerable<Person> Persons {get;}
    ... // fetch other data you store in your storage
}

这种存储通常被称为存储库,在仓库的意义上,您可以在其中存储项目,之后可以原封不动地获取它们。从您的 CSV 文件中获取数据的 class 将如下所示:

class MyCsvRepository : IMyDataFetcher  // TODO: invent a proper name
{
    public string FileName {get; set;}

    public IEnumerable<Person> Persons
    {
        get {...}
    }
}

在 get 中,您打开 CSV 文件并逐行阅读 return 人。如果需要,你可以聪明地记住读取的行,所以下次你需要 Persons 时,你不需要再次读取文件,但这超出了这个问题的范围

由于您的存储库 class 没有很多功能,为此编写单元测试非常容易,尤其是针对未找到文件、空文件、只有一个人的文件、文件的测试与人等以外的其他记录

执行查询的 class 与存储分开。它只知道,你可以通过某种方式获得一系列人物:

class MyPersonSelector
{
    public IMyDataFetcher Storage {get; set;}

    public IEnumerable<Person> Females
    {
        get => this.Storage.Where(person => person.Gender == Gender.Female);
    }

    public IEnumerable<Person> Adults
    {
        get => this.Storage.Where(person => person.Age > 21);
    }

    // etc.
}

对于您的单元测试,您不需要 CSV 文件,只需将其设为智能列表即可。

例如:

要求1:如果存储只包含男性,属性女性应该return一个空序列。

单元测试:

IEnumerable<Person> malesOnlyStorage = new List<Person>()
{
    new Person() {Gender = Gender.Male, ...},
    new Person() {Gender = Gender.Male, ...},
    new Person() {Gender = Gender.Male, ...},
}

MyPersonSelector testObject = new MyPersonSelector
{
    Storage = malesOnlyStorage,
};

IEnumerable<Person> fetchedFemales = testObject.Females;

// fetchedFemales should be empty
Assert.IsFalse(fetchedFemales.Any());  // this depends on the test suite you use

总结

通过分离你的关注点,你有更小的 classes,它们只有一个任务。较小的 classes 具有较少的功能,因此单元测试也较小。

通过将存储与存储上的查询分开,您的软件支持任何类型的存储。因此,对于您的单元测试,您可以使用简单的列表。

您的软件将为将来的更改做好准备:如果您还需要支持来自 XML 文件或数据库的人员序列,您的查询仍然有效。如果您需要添加另一个查询,您的存储 class 不必更改,因此存储 class 的单元测试不必更改。