使用 FakeModel 创建测试对象





5.00/5 (6投票s)
使用 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();
在这里,我们确保 myObject
的 Foo
属性被设置为“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
和一个 ModelC
。ModelC
又会包含一个 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 创建 SimpleModel
或 ModelC
将使我们能够测试无限循环,如果它不能很快结束,那么它就是坏的。
[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);
检查 ModelC
的 ModelB
Assert.IsNotNull(obj.c.b);
我们看到测试通过了,这意味着 SimpleModel
的 ModelC
的 ModelB
仍然被创建,因为它没有运行会导致无限循环的路径。这非常智能。
关注点
虽然这些演示展示了 FakeModel 的功能,但它们并未突出其在实际应用中的用途。许多此类应用程序会变得非常线性,尤其是那些专门针对单个线性过程的应用程序。如果您需要创建 15 或 20 个模型来测试这些线性系统中的最终过程,那么 FakeModel 将为您创建它们,而且更好的是,它会模拟您永远不知道用户将输入什么数据的这一事实,通过这些测试,您就可以安心了。
我最喜欢 FakeModel 的地方是作者的透明度。在他的个人网站的 FakeModel 页面上,他讨论了他计划如何发展 FakeModel,并且如果您联系他,他也乐于接受建议。我无法说他对这些建议的响应有多快,因为我还没有尝试过联系。话虽如此,我将发送一个关于模拟抽象类型和接口的请求。
FakeModel 目前不处理集合,但这已在未来的版本中承诺,而且希望不会太遥远。我们还被承诺了一些其他功能,包括更灵活的设置选项。
我应该使用它吗?
我们已经开始在我们维护的两个应用程序中使用 FakeModel。它并不完美,但话说回来,它仍然是 1.0 版本之前的。FakeModel 看起来会像威士忌一样成熟,作者的计划看起来很有趣,但谁知道这些更新会以何种速度到来。
我建议在需要处理 Data Annotations 的地方使用它,但在这个阶段我不会过度依赖它,大多数项目都会在早期阶段结束——但我真心希望这个不会。我希望作者能将 FakeModel 发展到一个他已经完成了他承诺的阶段,那时我会觉得它是我可以依靠的东西。
目前,我会说进行一次试驾,在需要的地方使用它,并关注此空间。
历史
- 13/09/09 - 初始版本。
- 13/09/17 - 添加了示例。