Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

We need mock LCM interfaces for unit testing #3546

Closed
david-german-tri opened this issue Sep 21, 2016 · 13 comments
Closed

We need mock LCM interfaces for unit testing #3546

david-german-tri opened this issue Sep 21, 2016 · 13 comments

Comments

@david-german-tri
Copy link
Contributor

Right now, there are two prevailing approaches for unit-testing code that sends or receives LCM:

  1. Actually put the bytes on the network. This is bad because it has side effects on any other program that happens to be listening.
  2. Break encapsulation to inspect the data the code under test believes it put on the network, or received from the network. This is bad because test-only API features are clutter at best, and hazards to the unwary at worst.

A better solution would be to use a mock and dependency injection.

@david-german-tri
Copy link
Contributor Author

Cleaning up #3494 would be a good first use case (but this feature should not block #3494).

@liangfok
Copy link
Contributor

liangfok commented Sep 21, 2016

Current Post

I'm trying to understand how #3494 could be modified to address this issue. Here's my first guess:

  1. Create a new abstract pure virtual parent class called DrakeLcmInterface that contains the following methods:
    • publish()
    • subscribe()
  2. Create two sub-classes of DrakeLcmInterface, one that contains a real lcm::LCM object and another one that contains a mock drake::LcmMock object. The mock object contains the following additional method:
    • get_last_message_sent()
  3. Modify RigidBodyTreeVisualizerLcm's constructor to take as input a DrakeLcmSubsystem instead of a lcm::LCM.

Is the above what's meant by "use a mock and dependency injection"?

Original Post

I'm trying to understand how #3494 could be modified to address this issue. Here's my first guess:

  1. Create a new abstract parent class called DrakeLcmSubsystem that contains the following methods:
    • get_last_message_sent()
    • publish()
    • subscribe()
  2. Create two sub-classes of DrakeLcmSubsystem, one that contains a real lcm::LCM object and another one that contains a mock drake::LcmMock object.
  3. Modify RigidBodyTreeVisualizerLcm's constructor to take as input a DrakeLcmSubsystem instead of a lcm::LCM.

Is the above what's meant by "use a mock and dependency injection"?

@jwnimmer-tri
Copy link
Collaborator

Nope.

The basic premise of mocking is that (essentially) no Drake code ever says lcm::LCM as a type. Instead, we program against DrakeLcmInterface, which offers comparable APIs to lcm::LCM. In real simulations, class DrakeLcm : public DrakeLcmInterface has-a lcm::LCM and proxies the methods over to it; in unit tests, class StubLcm : public DrakeLcmInterface mocks up its behavior, for example remembering all messages that are sent.

@liangfok
Copy link
Contributor

@jwnimmer-tri: I'm having trouble figuring out what's the difference between your description and my proposal. Is the only difference the name DrakeLcmSubsystem vs. DrakeLcmInterface? I would be happy to use DrakeLcmInterface.

@jwnimmer-tri
Copy link
Collaborator

jwnimmer-tri commented Sep 21, 2016

Ah yes. The name confused me -- they are indeed similar. It's important to use Interface -- it should really be entirely pure virtual.

Also typically the get_last_message_sent would not be on the interface, just the mock. Also, the mock would have storage directly, not has-a some other mock class.

@liangfok
Copy link
Contributor

Got it. Thanks. I'll go ahead and assign this to myself since I plan on implementing it after #3494 is merged.

@liangfok liangfok self-assigned this Sep 21, 2016
@liangfok
Copy link
Contributor

I'm going to start work on this now.

@liangfok
Copy link
Contributor

liangfok commented Sep 27, 2016

Must the mock LCM instance be so decoupled from LCM that it should be usable even if LCM is not present on the local machine? I ask because the lcm::LCM::subscribe() API includes things like lcm::ReceiveBuffer and lcm::Subscription that will be undefined if LCM is not present. Do I need to mock up all of these classes as well?

@jwnimmer-tri
Copy link
Collaborator

I suspect any reasonable testing with an LCM mock will be still using non-mocked Encode and Decode methods, so LCM-as-a-build-dependency is likely fine (though perhaps nice to remove, if we can).

However, perhaps you can offline explain your mock design, because I am surprised that such types would still be needed in mock-land.

@liangfok
Copy link
Contributor

From F2F meeting with @jwnimmer-tri, the mock LCM::Subscription API will look something like:

  template <class MessageType, class MessageHandlerClass>
  void subscribe(const std::string& channel,
      void (MessageHandlerClass::*handlerMethod)(const uint8_t* rbuf,
          const std::string& channel, const MessageType* msg),
      MessageHandlerClass* handler) = 0;

Note that lcm::Subscription and lcm::ReceiveBuffer will be pushed into a child class that uses the real LCM instance.

@liangfok
Copy link
Contributor

liangfok commented Oct 1, 2016

WIP branch is here: https://github.com/liangfok/drake/tree/feature/lcm_mock. I have the basic framework completed and the real LCM-encapsulating class implemented and unit tested. I still need to implement the mock version and to integrate the framework with all of the System 2.0 systems that use LCM.

@david-german-tri
Copy link
Contributor Author

🎉 very nice! thanks again!

@liangfok
Copy link
Contributor

liangfok commented Oct 8, 2016

You're welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants