具有不可预测的外部依赖项的单元测试代码
我参与的一个项目除其他外必须控制各种实验室仪器(机器人、阅读器等)。
大多数这些仪器是通过基于 DCOM 的驱动程序、串行端口或通过启动专有程序来控制的各种论点。其中一些程序或驱动程序包含模拟模式,有些则不包含。显然,我的开发计算机无法连接到所有仪器,虽然我可以为其驱动程序包含模拟模式的仪器启动虚拟机,但有些东西如果没有实际仪器就无法测试。
现在,我自己的代码主要不是关于仪器上的实际操作,而是关于启动操作、确保一切正常以及在许多仪器之间进行同步。它是用 Java 编写的,使用各种库与仪器及其驱动程序进行交互。
我想为各种仪器控制模块编写单元测试。然而,由于这些工具可能会以多种方式失败(其中一些有记录,一些没有),因为代码依赖于这些部分随机的输出,所以我对如何为这些部分编写单元测试感到有点迷失。我的代码。我考虑过以下解决方案:
- 仅使用连接的实际仪器进行测试,可能是最准确的方法,但它根本不实用(在读卡器中插入板,运行单元测试,移除板,运行单元测试等...),更不用说潜在的危险了,
- 使用模拟对象来模拟实际与事物通信的部分;虽然这个肯定更容易实现(和运行),但它可能无法重现全部潜在故障(如上所述,很多都没有记录,这有时会导致糟糕的意外)
虽然我目前正在考虑去对于后者,我错过了什么吗?有更好的方法吗?
I am involved with a project which must, among other things, controlling various laboratory instruments (robots, readers, etc...)
Most of these instruments are controlled either through DCOM-based drivers, the serial port, or by launching proprietary programs with various arguments. Some of these programs or drivers include simulation mode, some don't. Obviously, my development computer cannot be connected to all of the instruments, and while I can fire up virtual machines for the instruments whose drivers include a simulation mode, some stuff cannot be tested without the actual instrument.
Now, my own code is mostly not about the actual operations on the instruments, but about starting operations, making sure everything is fine, and synchronising between the lot of them. It is written in Java, using various libraries to interface with the instruments and their drivers.
I want to write unit tests for the various instrument control modules. However, because the instruments can fail in many ways (some of which are documented, some of which aren't), because the code depends on these partially random outputs, I am a bit lost regarding how to write unit tests for these parts of my code. I have considered the following solutions:
- only test with actual instruments connected, possibly the most accurate method, but it is not practical at all (insert plate in reader, run unit test, remove plate, run unit test, etc...), not to mention potentially dangerous,
- use a mock object to simulate the part that actually communicates with the thing; while this one is definitely easier to implement (and run), it may not be able to reproduce the full range of potential failures (as mentioned above, a lot is undocumented, which can sometimes cause bad surprises)
While I am currently thinking of going with the latter, am I missing something? Is there a better way to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您的两个要点都是有效的选项,但它们各自代表两种不同类型的测试。
在非常高的层面上,使用 Mock 对象(根据你的第二个要点)对于单元测试非常有用——这只是测试你的代码(即被测系统,或 SUT),而不是其他任何无关的东西。任何其他依赖项都被模拟掉。然后,您可以编写测试用例来抛出您能想到的尽可能多的不同错误条件(当然,也可以测试“快乐路径”)。不幸的是,您的错误条件范围没有记录,您应该尽力减少这种情况。每当您使用实际外部设备遇到新的错误情况时,您应该弄清楚如何通过代码重现它,然后编写另一个新的单元测试以通过模拟框架重新创建该情况。
此外,使用连接的实际仪器进行测试(根据您的第一个要点)对于集成测试非常有用 - 这更多的是与实际的外部依赖项一起测试您的代码。
一般来说,单元测试应该很快(理想情况下,在 10 分钟内编译代码并运行整个单元测试套件。)这意味着,如果您编写的任何新代码导致问题,您将从单元测试中快速获得反馈任何测试失败。就其本质而言,集成测试可能需要更长的时间(例如,如果您的一个外部设备需要 1 分钟来计算结果或执行任务,并且您正在测试 15 组不同的输入,则需要 15 分钟)您的 CI 服务器(您应该拥有自动编译和运行所有测试的服务器之一)应该在提交到源代码控制存储库时自动触发。它应该一步编译并运行单元测试。该部分完成后,它应该为您提供反馈(好或坏),然后如果单元测试全部通过,它应该自动启动您的集成测试。这假设有一个实际设备连接到您的 CI 服务器,或者有一个合适的替代设备(无论这在您的特定环境中意味着什么)。
希望有所帮助。
You two bullet points are both valid options, but they each represent two different kinds of testing.
At a very high level, using Mock objects (per your second bullet point) is great for Unit Testing -- which is simply testing your code (which is the System Under Test, or SUT), and nothing else extraneous to it. Any other dependencies are Mocked out. You can then write test cases to throw as many different error conditions as you can think of (as well as testing the "happy path," of course). The fact that your domain of error conditions is undocumented is unfortunate, and something that you should work to curtail as best as possible. Every time you run into a new error condition with the actual external device, you should figure out how to reproduce it via code, and then write another new unit test for recreating that condition through your mock framework.
Further, testing with the actual instruments connected (per your first bullet point) is great for Integration Testing -- which is more testing your code alongside the actual external dependencies.
In general, Unit Testing should be quick (ideally, under 10 minutes to compile your code and run your entire unit test suite.) This means that you'll get feedback quickly from your unit tests, should any new code you've written cause any tests to fail. Integration Testing, by its nature, can take longer (if, for example, one of your external devices takes 1 minute to compute a result or perform a task, and you have 15 different sets of inputs you're testing, that's 15 minutes right there for one small suite of tests.) Your CI server (you should have one of those that automatically compiles and runs all tests) should automatically be triggered upon commit to your source control repository. It should compile and run the unit tests as one step. After that part is done, it should provide you feedback (good or bad), and then if the unit tests all pass, it should automatically kick off your integration tests. This assumes that there is either an actual device connected to your CI server, or a suitable replacement (whatever that means in your particular environment.)
Hope that helps.
如果您使用模拟,那么您可以替换不同的模拟以实现不同的性能。也就是说,您的测试将是一致的。这很有价值,因为针对随机执行的系统运行测试不会给您带来安全感。每次运行都可以/将执行不同的代码路径。
由于您事先不知道所有故障场景,因此我认为有两种(非排他性)场景:
If you're using mocks then you can substitute different mocks to perform differently. That is, your tests will be consistent. That's valuable since running tests against a randomly performing system is not going to give you a sense of security. Each run can/will execute a different code path.
Since you don't know all the failure scenarios in advance, I think there are two (non-exclusive) scenarios:
根据定义,您无法对未预期的内容进行单元测试。
第二种方法适用于单元测试。连接实际仪器使其充其量成为集成测试。
拆分依赖关系,以便您可以创建一个假仪器,然后以尽可能多的方式使其模拟现实。随着您对现实的理解不断提高,请更新您的假象并添加测试来应对这种情况。 (在某些情况下嘲笑可能是合适的,而在其他情况下则可能是伪造的。)
You can't unit test something you haven't anticipated, by definition.
The second approach is appropriate for unit tests. Having the actual instruments connected makes it at best an integration test.
Split out the dependencies so that you can create a fake instrument, and then make that simulate reality in as many ways as you can. As your understanding of reality improves, update your fake and add tests to cope with that situation. (Mocking may be appropriate in some cases, faking in others.)