1

I was programming today and encountered something that just feels like I'm doing something wrong (maybe?). I've encountered this situation before, but I wanted to reach out and ask if there's a better way to handle this. I have a situation where I realized my function direct_data could save a lot of execution time by exiting early in situations where there is no room on the two upstream queues, so I wrote a test case:

TEST(data_director_test_group, transmission_and_storage_both_full_do_nothing) {
    // Arrange
    intakeQueue = QueueMock_Create(1, sizeof(Count));
    transmissionQueue = QueueMock_Create(1, sizeof(Count));
    storageQueue = QueueMock_Create(1, sizeof(Count));

    // Mock Expectations
    // create mock that says intake queue has 1 element
    mock().expectOneCall("Queue_Length")
            .withPointerParameter("queue", intakeQueue)
            .andReturnValue(1);

    // create mock that says transmission queue has no space available
    mock().expectOneCall("Queue_SpacesAvailable")
            .withPointerParameter("queue", transmissionQueue)
            .andReturnValue(0);

    // create mock that says storage queue has no space available
    mock().expectOneCall("Queue_SpacesAvailable")
            .withPointerParameter("queue", storageQueue)
            .andReturnValue(0);

    mock().expectNoCall("Queue_Pop");
    mock().expectNoCall("Queue_Push");

    // Act
    direct_data(
            true,
            intakeQueue,
            transmissionQueue,
            storageQueue
    );
}

I then added this short piece of code near the beginning of direct_data:

uint64_t transmissionAvailable = Queue_SpacesAvailable(transmissionQueue);
if (transmissionAvailable == 0 && Queue_SpacesAvailable(storageQueue) == 0) return;

The issue is this causes a multitude of other older test cases to fail because it now requires me to add the mock expectation, but saying that these calls to the Queue_SpacesAvailable function return 1 entry. To make it even worse, in some tests I have to pick and choose whether transmission or storage queue has an element, not both, because it would affect the old functionality of the test (and actually fail because the if statement's structure, it would be inefficient to force it to call both transmission and storage). I would be less interested in efficiency if I wasn't working on a MCU, but I've seen this situation come up in my development in PHP as well. Looking for some guidance as to what I may be doing wrong here... Thanks!

3 Answers 3

7

Me and lot of others are heavily against using Mocks. We consider mocks as sign of bad design and testing practice. What you describe is one of the reasons why. Mocks couple test way too tightly with implementation and they have tendency to break when even small changes are made to the code.

The alternatives are few:

  • Redesign your code as not to need dependencies at all, passing parameters and receiving commands to be executed on external systems
  • Use real dependencies, when those dependencies are not too slow or require external services
  • Create and use Fakes that implement same behavior, but take shortcuts to make testing fast and isolated. In-memory database or service implementation are common example.

In your case, I don't see why aren't you using real queues. Or queues simulated in-memory. Your previous tests wouldn't need to be changed, because their upstream queues would be already empty and would be returning they have available space without you having to make change to every test.

1
  • I like this point, I think I jumped to a mock far too quickly when a fake queue would have sufficed. What would be nice is to have a fake/mock/(spy?) combination where I can do some assertions on order of operations, but otherwise just check that it put the dependency that was injected in the expected state. Nothing is preventing me from writing that though, something I can play around with...
    – jrgilman
    Jul 7, 2023 at 16:59
4

I do not know your business context, but it sure sounds like you've been overly descriptive of your expectation (i.e. your assertions), straying from observing public behavior down to testing the private implementation details.

That's not wrong per se, but it clashes with the idea that you would be able to refactor the code the way you did without any tests breaking.

Either you test every single component interaction and then accept that you have to refactor tests when the components interact differently; or you stop bothering to test private component interactions and only look at public behavior.

The choice is yours.

1
  • I appreciate the response, this helped put things in perspective. One of the issues is I'm definitely getting far too into the specifics of the internals of the CUT, but in some situations I want to say that it should be more efficient. It seems like I need to simply use a fake implementation of the queue for the majority of this and only use a mock when I really care about internal implementation details for some reason.
    – jrgilman
    Jul 7, 2023 at 17:06
2

Does your software work or not? Do your unit tests pass or fail?

If your software works and your unit tests fail, then your unit tests are wrong. In your case, it seems your software works correctly but not in the way many unit tests expect. So you’d need to change that expectation of your unit tests.

(Your unit tests shouldn’t have broken, but no code, including unit testing code, is perfect, so you fix it and learn from it).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.