注释
以下是我的一些想法:
然后可以通过让 PC 应用程序捕获结果并将其与显示失败/通过的“答案”表进行比较来自动执行上述过程。 |
有像 http://code.google.com/p/arduinounit/这样的东西,但它们占用空间,GRBL 已经填满了小 MCU。:) 我们总是可以采用无硬件方式,只对库进行单元测试。可以完全自动化,但我们离实际结果有多近? 好吧,它开放讨论…… |
是否值得考虑模拟。取决于所需 或者代码的某些方面(不是全部)应该 我希望我是有道理的,目前正在走路、打字和思考
|
“在 PC 上编译”方法是有道理的,但大多数时候是无用的,原因有很多,例如 avr-gcc 中存在 GCC 错误,但在 gcc 和硬件行为细节中丢失/未触发。 simulavr 方法很有意义,特别是如果测试被设计成独立的(即它们只依赖于一些 G 代码被运送并且 GPIO 以非常明确的方式因此而改变)。最新的 simulavr 知道 ATmega328p,所以我们应该没问题。以前版本的 Atmel Studio (5.x IIRC) 还带有功能齐全且可编写脚本的模拟器,因此也可以通过这种方式取得一些进展。 |
我不认为我可以为这次谈话做出太多贡献,因为这对我来说是一个全新的领域。我不是专业程序员,所以我不知道你们都用来验证代码的一些更高级的技术。我处理的大多数东西都很小,一个人就可以掌握和维护。Grbl 可以说足够小了,至少目前是这样。 我认为这是我们选择保留非常有限且规模很小的主要原因之一。我们可以在一个小的代码库中解决一些最困难的问题,这最终使我们变得灵活。我认为随着我们在 ARM 上的规模越来越大,开始自动化一些测试任务是个好主意,但我不知道最好的方法是什么。 就我自己而言,我在编写代码时真的没有做任何特别的事情。按照某些标准,它可能有点愚蠢和过时。我只是编译代码并将其闪存到 Arduino 上,并验证新代码是否按预期工作。通过翻转 I/O 引脚并在示波器上测量时间来完成时序和代码效率测量。除此之外,我谨慎地尝试将功能分开,这样新代码就不会影响其他进程。如果它们确实必须交织在一起,我会将其保持在绝对最低限度并尽可能简单。作为最终测试,我在几台机器上使用极端情况下的“编织”g 代码程序(很多曲线)进行最终校对。 这肯定会很耗时,所以我很高兴听到我们有什么样的选择。 |
我处理这个问题的方法介于 DIY 黑客和“真正的”软件工程师之间: 我发现在嵌入式世界中,大多数“调试”实际上是“从一开始就编写正确的代码”(比如@chamnit提到)因为在嵌入式芯片中没有太多的主机调试空间。当然,如果您碰巧继承了一笔财富,您总是可以购买“官方”工具(debugWire 等)——但这超出了当前讨论的范围。 ARM 是另一回事(就像 XMegas),因为它有一个成熟的 JTAG 接口,实际上,它可以像计算机中的 CPU 一样进行调试:步进、检查变量、添加条件断点等。工作。此外,大多数 ARM 都有足够大的 RAM(或者,对于那些不能使用外部 RAM 芯片的,电路板有),您可以将代码上传到 RAM 中,从而使速度更快(更不用说使闪存持续时间更长)。此外,由于它是一个 32 位架构,gcc 的行为与主机的 gcc 非常相似,因此无论存在什么编译器错误,主机和 ARM gcc 上都有可能出现,因此在主持人,然后考虑这些结果是值得的。 |
是的,我喜欢哪里@csdexter正在这样做。这几乎就是 grbl 自动测试的另一个大挑战是有很多
|
@kitizz您所描述的可以通过依赖注入来解决。使用依赖注入,您可以通过参数传递函数所需的一切。这有助于单元测试过程,因为单元测试现在可以始终如一地测试与将传递给应用程序内部函数的相同输入。 DI 和 UT 齐头并进。DI 保持测试输入的一致性,UT 确保我们坚持使用 DI,因为 UT 无法访问全局变量。 |
是的,我想我想知道通过使用全局变量可以节省多少内存。如果我有空闲时间,我很乐意重构 grbl
|
我用于在嵌入式系统中管理全局变量的一种技术是将变量组隔离为单例。单例是只能有一个实例的结构(或对象)。因此,与其拥有一堆全局变量,不如将它们收集在一个整洁的包中。有些人认为使用单例是坏巫术(他们称之为“反模式”,我更喜欢前一个术语),但话又说回来,全局变量也可以这样说。至少对于单身人士,您可以将它们全部收集在一起。许多 grbl 实际上是这样工作的——比如配置值,但这些结构实际上并不称为单例(如果我弄错了请纠正我)。 单例的使用停止了依赖注入的一步——它们实际上并没有传递给函数——它们只是被假定存在。传递给函数在运行时会稍微降低 CPU 效率,因为堆栈帧会更大。还有其他可能的运行时效率低下 – 例如间接寻址与直接寻址。但是这些对于大多数功能来说可能无关紧要(分析的情况)。我不确定它是否会对 RAM 使用产生负面影响 – 我怀疑不会。 因此,拥有关于函数中使用哪些单例的标题文档将允许某种范围管理知道在给定单元测试期间需要担心哪些变量。当然,展开调用序列是必要的,而且可能会很棘手。 我发现另一件有用的事情是将单元测试代码放在每个模块的末尾,只有在定义了 __UNIT_TEST_XXXXX 时(通常在 module.h 标头中)才使用#ifdef。在 inits 之后但在系统主循环之前的 main 中有一个 _unit_test() 函数。通过在测试中有选择地编译,您可以在测试期间管理 FLASH 占用空间,并且不会向运行时占用空间添加任何内容。 最后,我通过在初始化期间预加载串行缓冲区来伪造初始串行输入。代码开始然后开始执行指示的函数。这对于设置模拟很有用。AVRStudio4 为 mega 和 xmega 部件提供了一个非常好的模拟环境。AtmelStudio6 也可以,但有一些限制尚未解决(例如结构中字符串的良好 ASCII 显示,以及其他一些限制)。 您可以查看 TInyG 代码库中的示例。测试有些地方有点乱,但我合理化它不是运行时代码的一部分,所以我可以变得草率。 奥尔登 |
不不不,单例对于单元测试来说和全局变量一样糟糕。你 一些文献:http: http://tech.puredanger.com/2007/07/03/pattern-hate-singleton/ http://googletesting.blogspot.com.au/2008/11/clean-code-talks-global-state-and.html
|
我明白作者提出的观点,但让我稍微反驳一下。另外,我并不是说单例是编码的“正确方法”,我只是说如果您使用全局变量,它们是管理它们的更好方法。 此外,作者正在处理的环境中,在这里和那里处理 DI 的几个字节和周期是无关紧要的——我们不是。或者至少我们可能不是——需要分析。权衡是,在紧密的嵌入式环境中,使用 DI “免费”获得的东西 – 依赖跟踪、隔离、解耦……以非零成本出现。然而,DI 的好处是必须手动完成,如果您使用全局/单例,则会变得更加劳动密集且容易出错。我只是质疑权衡。在任何一种情况下,您仍然需要管理测试的初始条件——在 DI 情况下,它们是为您拼写出来的,在另一种情况下,您必须非常仔细地跟踪它们。 |
是的,就性能而言,这就是我对 DI 的疑惑。
|
我认为这可能是一种混合。两种样式都有崩溃案例。单例崩溃是依赖图变得如此复杂以至于你无法合理地管理它并且你永远不确定你的测试条件是什么。DI 崩溃是你变得如此沉重以至于每个函数调用在循环和堆栈空间上变得太昂贵 – 特别是当你嵌套时。在 JVM 中,这无关紧要,但当堆栈应该保持在 256 字节以下(选择一个数字)时,它就可以了。 我怀疑正确的答案是两者的谨慎结合。很高兴知道规则,这样您就可以知道何时打破它们。例如,我们真的要将设置结构传递给从中读取的每个函数吗? |
两个要点: 无论如何,单例是一个有争议的问题,因为 grbl 不是面向对象的代码 我和你一样关心性能和 DI。这就是为什么我想 2013 年 3 月 23 日星期六晚上 10:01,Alden Hart notifications@github.com写道:
|
我认为研究使用 Atmel Studio 模拟器和 Stimuli 文件可能会有一些好处?您在模拟器中获得处理器、IO 和内存视图。刺激文件提供输入和操作,然后记录下来。总体而言,Atmel Studio 提供了一个更好的开发环境。它本质上是 Visual Studio 2010,但有一些限制。至少它是一个出色的调试环境,具有完整的执行和数据断点。而且它是免费的 |
我刚刚尝试在 Atmel Studio 中使用模拟器,如果我们伪造 serial_read()(即仅限模拟器的子例程)将数据馈送到 protocol.c 的第 108 行,那么您可以将代码块/行输入到仅限模拟器的串行 -行变量并将其处理为就好像它来自串行端口一样。在 IO 视图中,您可以看到各种端口位在上升和下降。 |
前几天,我们讨论了下一步的方向,其中一个主题是单元测试。
单元测试是我日常开发中不可或缺的一部分,但作为一名全职嵌入式开发人员,我正在努力将其融入我当前和未来的项目中。
关于我们如何实施这个的任何建议?