65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 FakeModel 创建测试对象

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013年12月17日

CPOL

8分钟阅读

viewsIcon

14871

使用 FakeModel 为你创建测试数据的介绍。

引言

组织并不总是有专职的测试人员,事实上,开发人员经常需要自己覆盖测试。创建测试数据可能很麻烦,尤其是当涉及到复杂的线性单一目的的大型系统网络时。我的意思是,我每天都要处理的七个系统中的任何一个。我们都努力以正确的方式创建完美的系统,但不幸的是,管理者们将给开发人员的生活带来困难作为他们的生存目的。

有时,我们需要一组数据来运行一个过程的测试。FakeModel 就像其他可用的数据创建套件一样,允许我们以一种简单的方式做到这一点,它拥有一个流畅的 API,可以与 LINQ 相媲美。

背景

市面上存在更成熟的测试数据创建套件,但我所知的没有一个能够识别 DataAnnotations 并妥善处理它们,如果使用 ORM,它非常适合创建数据。FakeModel 会关注附加到属性上的数据注解并作出相应的反应。

但正如我所说,它还处于起步阶段,目前版本是 0.0.5,这是昨晚的。当我抱怨很难找到一个不会忽略我的注解的测试数据套件时,我的大学讲师向我推荐了 FakeModel。我不知道他是怎么找到的。

V0.0.4 在创建类属性时有一个 bug,它会创建无限循环,所以我不得不一直关闭这个功能,这很容易做到;

FakeSetup.AssignClassProperty = false;

这个问题在 V0.0.5 中已修复,发布说明中提到,无限循环已被检测并处理,通过结束新对象的创建,这效果很好。

使用代码

FakeModel 可以在 NuGet 上找到。主页可以在 Adam Riddick 的网站上找到。虽然一切都已涵盖,但我将快速介绍一下 FakeModel 的易用性。

var myObject = Fake.Begin().Build()

这是标准的 FakeModel 操作,它将返回一个包含已分配属性的对象。

我们也可以设置某些属性的特定值,或者告诉 FakeModel 不要为它们生成值,如下所示

var myObject = Fake.Begin().Assign(x => x.Foo == "Bar").Ignore(x => x.myProp).Build();

在这里,我们确保 myObjectFoo 属性被设置为“Bar”值,而 myObject 对象中的 myProp 属性将被忽略,因此它将保持其类型的默认值。

FakeModel 还有许多其他功能,例如允许您设置构造函数参数,或者在您不设置且它们是必需的时创建它们。您可以在创建对象时调用方法,并且可以分配给私有或只读属性。

使用 Visual Studio 集成测试套件和 FakeModel 进行单元测试

我将从演示使用 FakeModel 创建一个对象开始,用于 Visual Studio 集成测试套件的测试场景,目前这将是一个简单的单模型解决方案,但之后我会更深入地介绍更复杂的场景。

单模型场景

在这种简单的场景中,我们将测试一个对象扩展方法的有效性,但我们不只希望测试一个具有强制值的场景,因为使用其他值可能会导致代码出错。

我们从一个简单的模型开始,名为 SimpleModel

public class SimpleModel
{
    [Range(0, 15)]
    public int rangedNumber {get; set;}
    public int number {get; set;}
    public long result {get; set;}
}

在这里,我们有一个可能(可能设计得很差)的模型,它接受一个介于 0 和 15 之间的 rangedNumber,然后可以保存一个结果,我们可以安全地假设结果将是这两个数字的未知计算。

SimpleModelExtensions 类中,我们有一个用于 SimpleModel 的扩展,用于计算结果的值。

public static long CalculateResult(this SimpleModel model)
{
    model.result = model.rangedNumber + model.numer;
    return model.result;
}

我们希望测试大量的数字,以确保它不会被某种组合所破坏。而不是手动生成这些数字,我们可以使用一个简单的循环来确保我们有一个不受控制的、随机选择的数据 - 就像我们在大量用户群体中可能遇到的数据一样。

为此,我们使用 FakeModel 在循环中创建多个对象,测试结果是否符合预期。

[TestMethod]
public void TestMethod1()
{
    for(int i = 0; i < 1000; i++)
    {
        SimpleModel obj = Fake<SimpleModel>.Begin().Build();
        obj.CalculateResult();
        Assert.AreEqual(obj.number + obj.rangedNumber, obj.result);
        Assert.IsTrue(obj.rangedNumber <= 15 && obj.rangedNumber >= 0);
    }
}

将数字从 1000 更改为更高的数字并运行测试,您将看到在每种情况下(除非我还没遇到这种情况),rangedNumber 始终会在设定的范围内。而且我们的扩展方法也能很好地完成它的工作。

尽管这是一个简单的场景,但它可以展示使用 FakeModel 进行多种数据情况测试的强大功能。在一个简单的模型中,通过输入一些随机数就可以轻松地测试一个范围。但是,当我们需要具有更多属性的多种模型时,FakeModel 确实能发挥其优势。

多模型场景

就像简单场景一样容易使用,它有点不现实,在现实世界中你可能不会遇到使用单个模型的应用程序。所以我们将基于这个模型来引入第二个和第三个模型,并在它们之间添加一些依赖关系。

SimpleModel 将被修改,以便它包含一个 ModelB 和一个 ModelCModelC 又会包含一个 ModelB。让我们看看 FakeModel 能否处理这些。

我们更新的 SimpleModel

public class SimpleModel
{
    [Range(0, 15)]
    public int rangedNumber {get; set;}
    public int number {get; set;}
    public long result {get; set;}
    public ModelB b {get; set;}
    public ModelC c {get; set;}
}

ModelB:

public class ModelB
{
    [Range(0, 15)]
    public int number {get; set;}
}

ModelC:

public class ModelC
{
    public ModelB b {get; set;}
}

我们将使用另一个单元测试来找出 FakeModel 能否处理这些关系

[TestMethod]
public void TestMethod2()
{
    for(int i = 0; i < 1000; i++)
    {
        SimleModel obj = Fake<SimpleModel>.Begin().Build();
        ModelC objC = Fake<ModelC>.Begin().Build();

        Assert.IsNotNull(obj.b);
        Assert.IsNotNull(obj.c);
        Assert.IsNotNull(obj.c.b);
        Assert.IsTrue(obj.b.rangedNumber <= 15 && obj.b.rangedNumber >=0);
        Assert.IsTrue(obj.c.b.rangedNumber <= 15 && obj.c.b.rangedNumber >=0);
    }
}

从这个简单的测试中,我们可以看到 FakeModel 能很好地处理 POCO 关系,并且仍然会尊重下一级属性。但在这种场景中,我们一切顺利。如果我们不顺利呢?

无限循环场景

无限循环会发生,这是事实。它必须是软件开发中最常见的 bug 之一(我没有事实依据,我只是凭空想象)。

FakeModel 声称能够处理无限循环,截至最新版本(0.0.5),让我们看看它的效果如何。

我们将修改 ModelC,使其同时接受一个 SimpleModel 和一个 ModelB。SimpleModel 有一个 ModelC 属性,这又需要一个 ModelC……当然,这会一直持续下去。

public class ModelC
{
    public ModelB b {get; set;}
    public SimpleModel simple {get; set;}
}

使用 FakeModel 创建 SimpleModelModelC 将使我们能够测试无限循环,如果它不能很快结束,那么它就是坏的。

[TestMethod]
public void TestMethod3()
{
    SimpleModel obj = Fake<SimpleModel>.Begin().Build();
    
    //Whats Happening?
    Assert.IsNotNull(obj);
    Assert.IsNotNull(obj.b);
    Assert.IsNotNull(obj.c);
    
    //How far does it go?
    Assert.IsNotNull(obj.c.Simple);
}

运行 TestMethod3 将显示结果,obj.c.simple 为 null,因此测试失败,但删除此行后测试可以通过。

这意味着什么?

这意味着 FakeModel 已经足够智能,能够理解我们的模型中存在无限循环。它识别出循环,并决定在早期阶段将其终止。

我们有一个 SimpleModel,其中有一个 ModelC,但第二层的 SimpleModel 没有自己的 ModelC。如果我们更改导致测试失败的那一行

Assert.IsNotNull(obj.c.simple);

检查 ModelCModelB

Assert.IsNotNull(obj.c.b);

我们看到测试通过了,这意味着 SimpleModelModelCModelB 仍然被创建,因为它没有运行会导致无限循环的路径。这非常智能。

关注点

虽然这些演示展示了 FakeModel 的功能,但它们并未突出其在实际应用中的用途。许多此类应用程序会变得非常线性,尤其是那些专门针对单个线性过程的应用程序。如果您需要创建 15 或 20 个模型来测试这些线性系统中的最终过程,那么 FakeModel 将为您创建它们,而且更好的是,它会模拟您永远不知道用户将输入什么数据的这一事实,通过这些测试,您就可以安心了。

我最喜欢 FakeModel 的地方是作者的透明度。在他的个人网站的 FakeModel 页面上,他讨论了他计划如何发展 FakeModel,并且如果您联系他,他也乐于接受建议。我无法说他对这些建议的响应有多快,因为我还没有尝试过联系。话虽如此,我将发送一个关于模拟抽象类型和接口的请求。

FakeModel 目前不处理集合,但这已在未来的版本中承诺,而且希望不会太遥远。我们还被承诺了一些其他功能,包括更灵活的设置选项。

我应该使用它吗?

我们已经开始在我们维护的两个应用程序中使用 FakeModel。它并不完美,但话说回来,它仍然是 1.0 版本之前的。FakeModel 看起来会像威士忌一样成熟,作者的计划看起来很有趣,但谁知道这些更新会以何种速度到来。

我建议在需要处理 Data Annotations 的地方使用它,但在这个阶段我不会过度依赖它,大多数项目都会在早期阶段结束——但我真心希望这个不会。我希望作者能将 FakeModel 发展到一个他已经完成了他承诺的阶段,那时我会觉得它是我可以依靠的东西。

目前,我会说进行一次试驾,在需要的地方使用它,并关注此空间。

历史

  • 13/09/09 - 初始版本。
  • 13/09/17 - 添加了示例。
© . All rights reserved.