注释
以下是我的一些想法:
然后可以通过让 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 中,从而使事情变得更快(更不用说让 Flash 持续更长时间了)。此外,由于它是 32 位架构,gcc 的行为与主机的 gcc 非常相似,因此任何存在的编译器错误都更有可能在主机和 ARM gcc 上都出现,因此在主持人,然后认为这些结果值得。 |
是的,我喜欢在哪里@csdexter正在这样做。这几乎就是 grbl 中自动测试的另一大挑战是有很多
|
@kitizz您所描述的可以通过依赖注入来解决。使用依赖注入,您可以通过参数传入函数所需的所有内容。这有助于单元测试过程,因为单元测试现在可以一致地测试与将传递给应用程序内部函数的输入相同的输入。 DI 和 UT 齐头并进。DI 保持测试输入的一致性,UT 确保我们坚持使用 DI,因为 UT 将无法访问全局变量。 |
是的,我想我想知道拥有全局变量可以节省多少内存。如果我有空闲时间,我很乐意重构 grbl
|
我在嵌入式系统中用于管理全局变量的一种技术是将变量组隔离为单例。单例是只能有一个实例的结构(或对象)。因此,与其拥有一堆全局变量,不如将它们收集在一个整洁的包中。有些人认为使用单例是不好的巫术(他们称其为“反模式”,我更喜欢前一个术语),但话说回来,全局变量也可以这样说。至少对于单身人士,您可以将它们全部收集在整齐的捆绑中。很多 grbl 实际上是以这种方式工作的——就像配置值一样,但这些结构实际上并不称为单例(如果我弄错了,请纠正我)。 单例的使用比依赖注入少了一步——它们实际上并没有传递给函数——它们只是假设在那里。由于堆栈帧会更大,因此在运行时传递给函数会稍微降低 CPU 效率。还有其他可能的运行时效率低下 – 例如间接寻址与直接寻址。但这些对于大多数功能可能无关紧要(分析的情况)。我不确定它是否会对 RAM 使用产生负面影响——我怀疑不会。 因此,拥有关于在函数中使用哪些单例的标题文档将允许某种范围管理知道在给定的单元测试期间需要担心哪些变量。当然,展开调用序列是必要的,而且会变得很麻烦。 我发现另一件有用的事情是将单元测试代码放在每个模块的末尾,只有在定义了 __UNIT_TEST_XXXXX 时(通常在 module.h 头文件中),它才会被 #ifdef’d 放入。在 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通知@github.com 写道:
|
我认为研究使用 Atmel Studio 模拟器和刺激文件可能会有一些好处?您可以在模拟器中获得处理器、IO 和内存视图。Stimuli 文件提供输入和操作,然后将其记录下来。一般来说,Atmel Studio 总体上提供了一个更好的开发环境。它本质上是带有一些限制的 Visual Studio 2010。至少它是一个出色的调试环境,具有完整的执行和数据断点。而且它是免费的 |
我刚刚尝试在 Atmel Studio 中使用模拟器,如果我们伪造 serial_read()(即仅模拟器子例程)将数据馈送到 protocol.c 的第 108 行,那么您可以将代码块/行输入到仅模拟器串行-行变量,并像从串行端口进来一样处理它。在 IO 视图中,您可以看到各种端口位的上升和下降。 |
Protoneer 评论 on 22 Mar 2013
前几天我们讨论了下一步该去哪里,其中一个主题是单元测试。
单元测试是我日常开发中不可或缺的一部分,但作为一名全职嵌入式开发人员,我正试图将其融入我当前和未来的项目中。
关于我们如何实现这一点的任何建议?