2 个版本
0.1.1 | 2023 年 7 月 12 日 |
---|---|
0.1.0 | 2023 年 7 月 12 日 |
#192 在 模拟
52KB
357 行
caet
:因果测试器
这是什么?
caet
模拟一个由法官操作的世界,在其中您可以测试对象对一系列事件的响应行为,同时该对象反过来又影响世界。
我为什么要这样做?
caet
主要用于原型化和验证面向对象的分析和设计模型,因为先前的方法存在缺陷
- 任何类型的理论论证:快速简单(与实际编写代码相比),但可能是不准确或不完整的。惊喜。
- 编写代码:慢,昂贵,锁定。
- 编写单元测试:僵化,脆弱,锁定。
- 编写集成测试:通常不适用;需要搭建脚手架。
- 许多复杂的方法:有时你会觉得他们唯一关心的是卖给你他们的书。
那么,什么时候模拟是一种更好的方法呢?
- 当你想测试设计模型而不是实现时。
- 你有一个将复杂系统分解为可管理部分的建议。好吧,但你确定这是好的设计吗?你怎么知道?
- 那么,模拟一下看看会发生什么。
那么,如何模拟会更好?
- 不贵:你必须编写代码,但你不必处理实际的 I/O 或性能。只需定义表示事件和反应的数据类型,并为要测试的对象(以及法官,稍后会详细介绍)添加一些模拟代码。完成之后,您可以审查它,讨论它,丢弃它,或者保留它。
- 不锁定:您根本不需要连接到现有代码。如果您了解您的领域,您可以从头开始编写模拟代码,然后丢弃它或保留它作为文档。所以,尽管这个 crate 使用 Rust,并且您必须使用 Rust 编写模拟代码,但您不需要为实际的实现使用 Rust。我想您会欣赏这一点,如果您已经在进行一个大项目。
- 不具有刚性:大多数测试套件只是简单地回忆一系列事件并检查结果。《caet》允许你定义一个自定义的
Judge
机器,它可以像单元测试一样轻松,也可以像完全自主的对抗“上帝”一样努力,后者将战略性地尝试破坏你的系统。你必须自己完成。你必须自己编写Judge
,但这并不困难。 - 不具有脆弱性:这不仅仅是回忆一系列事件,而是使用实际的计算机程序进行检查。因此,这取决于你。如果你付出努力,你将获得一个健壮的测试套件。如果你不付出努力,你将看到如果你在不到一个小时的时间内真正开始编写代码,真实代码将是什么样子(当然,取决于复杂性)。
- 反馈:你可以在几小时内获得关于你的设计模型的反馈,就像我说的!当我亲自进行这些模拟时,我总是惊讶我的原始设计有多么缺乏,我喜欢快速的反馈周期,这样我就可以在实际完全不熟悉的领域内快速构建一些东西。大型项目比以前更容易应对。我希望你也喜欢。
- 这不是一本书:我这里没有卖书。我只是给你一个可以用来测试你的想法的工具。好吧,这里有一些自定义的哲学思想,但它主要是对老式的面向对象(OO)概念的改写,没有专有内容。
请解释一下它的哲学思想。
caet
基于以下原则
- 协议而非算法:我们解决的是一个交互式问题,而不是一个计算问题。换句话说,我们是在构建机器人,而不是破解LeetCode。
- 对象、宇宙、时间:我们以三种方式看待任何交互式问题
- 对象:这是被建模的系统。所有对象都是为了每个宇宙而专门化的。一个对象也可以称为事物、实体、代理等。对于它有很多名字,但本质上都是同一件事。
- 宇宙:这个术语有点含糊不清,但基本上是用一种非常简单的语言来表述的对外部世界的视图。如果你想的话,可以融入一些这个宇宙在其演化过程中遵循的绝对法则的概念,但
caet
假设没有任何内容。它也被称为环境、周围环境、上下文等。再次强调,很多名字,但都是同一件事。 - 时间:这个概念也模糊不清,你可以为这个概念添加很多内容以供自己使用,但就我们而言,它只是定义了事情发生的顺序。你可能想融入物理时间、同时性等。但如果你真的想建模并发和通信,
caet
不是你的工具,尽管你可以通过修改它来原型化这样的系统。
- 单例原则:一个宇宙中恰好有一个对象。所以如果你想建模通信,你必须间接地进行:首先将他们的集体行动转换为宇宙中的变化,然后让每个对象对这些变化做出反应。但如果你真的想建模并发和通信,
caet
不是你的工具,尽管你可以通过修改它来原型化这样的系统。 - 分而治之:一个对象可能由多个独立、简单、寿命较短的“宇宙”组成,类似于人体由多个独立的腔室组成,每个腔室与外界隔绝,其中不同的器官专门在其中休息。而且,这些“宇宙”的集体进化决定了更大复合对象的性质。这种分解可以递归进行,直到程序员决定一个准备好的依赖或模块可以处理它,或者“宇宙”不再需要交互,因此可以用一个可靠的函数调用替换。
- 生命周期:“宇宙”有一个生命周期,它可以诞生和消亡。同一类型的“宇宙”的独立实例可以同时存在,每个实例恰好包含一个对象。
- 对象的专门化:对象可以针对“宇宙”的类型进行专门化。这就是为什么你不能把肝脏放在胃酸里希望它能生存。肝脏在肝脏“宇宙”中工作,胃在胃“宇宙”中工作。
- 观察是被动、瞬时和可靠的:对象可以从“宇宙”中“感知”或“观察”事物,这是廉价的。实际上,对象可以可靠和瞬时地感知“宇宙”中的任何事物,但只能被动地;它不能主动选择要感知什么和何时感知,因为随机访问等于通过读取未来违反因果律。
- 反应是主动的,但既不是瞬时的也不是可靠的:然而,对象确实有一定的能动性。对象可以对“宇宙”进行“作用”,这既不是瞬时的也不是可靠的。《code>caet》正是测试对象这一行为的:对象的反应是否及时、有效和适当。由事件引起并导致对象动作的行为称为“反应”。
- 因果律:未来不能影响过去。《code>caet》所理解的因果律与以下任何一种(以及相互之间)完全相同
- 封装:“宇宙”可以向对象隐藏信息,对象可以向“宇宙”隐藏信息。
- 固有交互性:某些过程可能固有地具有交互性;它们也可能是敌对的,但不一定是。
- 状态:宇宙和对象都有状态,这不仅关乎保持信息,而且关乎对时间的敏感性。
最后一点可能有点令人惊讶。但,是的,这四个概念都是同一件事,一个单一的概念。
如何使用它?
首先,这更像是一个原型工具,而不是实际的测试工具。或者,这是一个在实际上编写代码之前“测试”(验证)你的想法的工具。所以,请记住这一点。
此外,这是一个Rust crate,所以你需要了解Rust才能使用它。
现在,在使用此工具之前,你需要进行一些概念性的工作。
- 你需要定义“宇宙”的语言。(现在我们可以将其称为
MyEvent
。)这听起来很吓人,但请记住,caet
实际上对“宇宙”有一个相当狭窄和特定的定义。因此,具体来说,你需要定义
- 一个表示事件的类型。这是“宇宙”的语言。(或者,严格地说,语言的“字母”部分。)请注意,
caet
将事件、消息和命令的概念合并在一起,并不区分它们。在实践中,这意味着对象反应的返回值使用与方法调用相同的数据类型来描述。
- 现在,您需要自行实现
Judge
特性。这个特性接收对象上最后发射的事件集合,对这些事件进行判断以确定是否接受,维护并发展内部宇宙,然后——如果它决定继续进行——为对象生成一个新事件(一个 挑战)。或者,停止模拟并报告其发现。 - 没有专门的“对象”特性,但在负责运行模拟的
judge
函数中,您可以传递这种类型的闭包:FnMut(MyEvent) -> Vec<MyEvent>
。对象将一次反应一个事件,生成一个有序的事件列表作为响应。这些事件如何与外界交织是由评判者决定的。或许令人惊讶的是,这就是定义对象所需做的所有事情。 - 使用
judge
函数运行测试。这个函数接收您的评判者实现和表示对象的闭包。它将运行模拟,直到评判者决定停止,之后将返回一个Outcome
结构。您可以检查Outcome
来查看模拟是否成功以及迭代次数,如果没有成功,则查看发生了什么错误。
关于为什么决定使用闭包作为对象的原因
我这样设计是为了隐藏对象可能具有的任何状态,因为,也许,您正在测试的对象实际上是一个现实世界系统,所以它可能根本不是计算机程序。因此,期望对象是一个可以实例化并传递的结构是不合理的。毕竟,它可能是一个无法复制粘贴的物理系统。
因此,让我们总结一下
- 在数据类型中定义所有事件。(我们称之为
MyEvent
。)命令、方法调用、信号、返回值,无论什么。把它们都塞进这个数据类型。 - 自行实现
Judge
特性。评判者就像上帝:它运行一个对抗性的宇宙,试图通过策略性地生成对象必须反应的事件来破坏您的对象。(或者,它可以是一个冷漠的宇宙,只是回忆一个预先记录的事件序列。您来决定。) - 将对象定义为类型
FnMut(MyEvent) -> Vec<MyEvent>
的闭包。返回值是对象发出的——如果有的话——事件的集合,按照它们发出的顺序列出。对象的目标是发出评判者接受的正确顺序的事件。 - 通过传递评判者和对象闭包的所有权来使用
judge
函数运行模拟。这个函数将让他们战斗,并返回一个您可以检查的Outcome
结构。
您应该注意的一点是,Judge
特性对对象类型一无所知,对象也不知道评判者。连接两者的唯一东西就是 judge
函数。
这意味着,任何评判者都可以测试任何对象,只要他们同意使用相同的事件类型,在本例中是 MyEvent
。希望这是一个强大的功能,因为它意味着您可以针对多个评判者测试您的对象,您也可以针对同一评判者测试多个对象。
在哪里可以找到示例
也许查看示例可以使这一点更清晰。
至于示例,有文档的主页,还有该crate本身的单元测试。
主页上的文档简单,但并未涵盖所有功能。单元测试更全面,但也更复杂。我建议您先从文档开始,然后转向单元测试。