开发区域

高级软件开发与MATLAB

不要嘲笑我!

大家好,好久不见了!感谢您的耐心,因为我们在博客上有很多很好的酝酿话题可以讨论。今天我很高兴地介绍David Hruska,他是一个令人兴奋的新的mocking框架的首席开发人员,这个框架是MATLAB R2017b的一部分,他在这里和你们一起探索它。享受吧!

你在写代码的时候会嘲笑别人吗?不,我不是在说嘲笑。我所说的是用模拟对象替换待测系统的依赖关系,以特定的方式驱动系统的行为,或验证它如何与合作者进行交互。MATLAB发布版2017a包含了一个模拟框架。让我们看一个实际的例子,看看这在实践中意味着什么。这是an的接口迭代器我们将使用的类。

classdef迭代器<处理属性(抽象,SetAccess =私人)值;结束方法(抽象)bool = hasNext(iter);推进(iter);结束结束

这是我们要测试的算法。它确定迭代器中是否存在值。

函数if (iter, value) = new (iter, value)迭代器的}, {“标量”},“hasValue”);validateattributes(价值,{“数字”}, {“非空的”},“hasValue”);bool = false;iter。hasNext iter.advance;如果iter。价值== value bool = true;结束结束结束

构建一个模拟

要构造一个mock,调用createMock的方法matlab.mock.TestCase类。通常,这将在派生类的测试方法中使用matlab.mock.TestCase.但是,为了在命令行进行交互试验,我们将首先使用forInteractiveUse静态方法获取TestCase实例。给…打电话createMock方法是创建模拟对象所需的全部内容。框架创建一个实现接口所需的抽象属性和方法的类,并构造一个实例。createMock也返回一个关联的行为对象,用于控制模拟。

testCase = matlab.mock.TestCase.forInteractiveUse;[mockIterator, behavior] = testCase.createMock(?Iterator);

为了让mock做一些有用的事情,我们需要定义它的行为。行为规范读起来像一句话:

进口matlab.mock.actions.AssignOutputs;when(get(behavior.Value), then(AssignOutputs(1), then(AssignOutputs(2), then(AssignOutputs(3)))));

这段代码是这样做的:当价值属性时,首先返回值1.下一次访问属性时,返回值为2.最后,返回3.用于任何后续的属性访问。

我们来建立hasNext方法返回真正的前三次它被调用之后。使用withAnyInputs指定无论传递给方法的任何额外输入都应执行此行为。

当(withAnyInputs(behavior.hasNext), then(repeat(3, AssignOutputs(true)), then(AssignOutputs(false))));

定义了一些基本行为后,我们现在可以使用mock来测试算法。

结果= hasValue(mockIterator, 2);testCase.verifyTrue(结果,“期望hasValue找到2。”);
交互式验证通过。

模拟迭代器被设置为返回1,然后2,然后3.;的hasValue函数找到了2;我们已经验证了这个结果。太棒了!

上面的例子使用了一种技术,有时被称为存根:我们设置了一个mock,以返回预定义的响应,以特定的方式驱动系统。我们也可以间谍关于系统如何与它的依赖项交互。让我们设置一个行为略有不同的新模拟,并将其传递给hasValue

[mockIterator, behavior] = testCase.createMock(?Iterator);当((behavior.Value),然后(AssignOutputs (1)));当(withAnyInputs(behavior.hasNext), then(repeat(10, AssignOutputs(true)), then(AssignOutputs(false))));结果= hasValue(mockIterator, 1);testCase.assertTrue(结果,“期望hasValue找到1。”);
通过交互式的断言。

在这里,我们定义了mock总是返回值1当它价值属性访问。的hasNext方法返回真正的第一个10它被调用的次数,表示有10迭代器中的值。期望有效地执行是合理的hasValue在找到第一个值并停止检查迭代器中剩余的元素后,执行“短路”操作。让我们看看到底发生了什么。当构建模拟时,它会自动开始记录关于其交互的信息。除了定义mock应该如何操作之外,行为也提供了获取间谍信息的途径。我们用这个信息来验证一下价值只访问一次,这是高效的实现应该做的。

进口matlab.mock.constraints.WasAccessed;testCase.verifyThat(行为。价值,WasAccessed (“WithCount”1),...“仅访问一次的期望值”。);
交互式验证失败。---------------- 测试诊断 : ---------------- 期望值只访问一次。--------------------- 框架的诊断 : --------------------- WasAccessed失败了。——>属性'Value'未被访问预期的次数。实际属性访问计数:10期望属性访问计数:1指定属性访问:PropertyGetBehavior 。价值

由于算法访问了属性,限定失败10次了。这可以通过在函数找到所需值的第一个实例后立即返回来解决。

函数if (iter, value) = iter, {迭代器的}, {“标量”},“efficientHasValue”);validateattributes(价值,{“数字”}, {“非空的”},“efficientHasValue”);bool = false;iter。hasNext iter.advance;如果iter。价值== value bool = true;%完成了!我们已经找到了价值。返回结束结束结束

回到原来的算法,让我们再写一个测试。这一次,让我们编写一个否定测试,以确保正确的错误检查在价值输入hasValue,拒绝空值。对于这个测试,我们甚至不打算使用Iterator,但我们确实需要提供一个有效的实例,因为该函数还会验证迭代器的输入。因此,我们可以简单地构造一个mock,但不定义任何行为。然后将mock传递给正在测试的函数。

dummyIterator = testCase.createMock迭代器(?);testCase.verifyError (@ () hasValue (dummyIterator []),MATLAB: hasValue: expectedNonempty);
交互式验证通过。

与手工编码模拟的比较

的手写模拟子类可以执行上面的测试迭代器接口。然而,这可能会导致以下两种情况之一:许多不同的mock(存根、间谍、赝品等)的清单,或者一个非常复杂的mock类,它可能比您试图测试的代码更复杂!例如,我们可能会试图重用手写的存根或监视上面的负面测试,这可能会导致不希望的耦合。通过使用mocking框架,我们实现了每个测试的独立性。

此外,随着设计的发展,接口可能会发生变化:可能会添加新的抽象属性或方法。或者访问权限可能会改变。这尤其可能发生在开发过程的早期。手工编写的模拟需要单独更新,以反映对界面的更改。另一方面,模拟框架每次都以正确的访问权限自动实现接口所需的所有抽象成员createMock被称为。

总而言之,与手工编码的模拟相比,模拟框架提供了

  • 不同测试的隔离和独立性
  • 用更少的额外文件来清除测试的可见性

费用是多少?这种便利是以性能为代价的:如果您经常重用完全相同的模拟类,手动编码将导致更快的测试执行时间。然而,模拟框架的开销可能很小,在大多数测试场景中微不足道。

这个框架还能做很多事情,所以文档.您在测试工作流中使用过模拟吗?请在下面的评论中告诉我们。




发布与MATLAB®R2017a

|
  • 打印
  • 发送电子邮件

评论

要留下评论,请点击在这里登录到您的MathWorks帐户或创建一个新帐户。