2

I am writing a module and I have a question about the best way to test a "non-atomic" function. My problem is that I am not sure how much and what specifically I should test because it being a complex function it is not easy to come up with all the possible combinations of expected cases.

I have unit tests for all the atomic components in my test suite. What should I test for the "non-atomic" function that integrates them?

  1. Tests that give the function some inputs and check expected outputs
  2. Tests that check that the "atomic" sub components have been called (using mocks?)

I believe the root cause of my issue is that this are not really unit tests but integration tests or some other type and so I should be thinking about this differently. Should I research some other type of testing to find best practice on how to think about this kind of problem?


A stylized example of my module is below and my question is if to test "non-atomic" I should give it some input and check output or I should mock A and B and check that it has called them?

def non-atomic(input):
    intermediate_result = A(input)
    output = B(intermediate_result)
    return output

def A(input):
    # Do something and return the result

def B(input):
    # Do something and return the result
1
  • In your example, are A() and B() ever expected to be directly called, or would it all be calls to non-atomic()? Put another way, if they're essentially private methods, I wouldn't test them. I tend to focus tests on behavior, not on implementations.
    – ernie
    Nov 15, 2021 at 18:13

2 Answers 2

3

Unit tests are about checking the public API of a function. In the function below, what matters is input and output. The rest are details that should be mostly hidden in your tests.

def non-atomic(input):
    intermediate_result = A(input)
    output = B(intermediate_result)
    return output

Thus what matters for you is to describe in your unit tests what behavior do you want to see in your non-atomic function; how your function achieve it is not of the business of the unit tests (so you can use them to refactor your solution).

If A and B are not exclusive of non-atomic (e.g. not a private method in Java), you may want to mock out A and B, since it's not of your interest to test their public API.

My problem is that I am not sure how much and what specifically I should test because it being a complex function it is not easy to come up with all the possible combinations of expected cases.

If A and B are part of non-atomic exclusively (e.g. a private method in Java), it doesn't matter you having full coverage as these functions are at the moment. The important part are the unit tests, and the behavior they describe. If you describe the problem you are trying to solve in the unit tests and your production code has parts are not exercised (e.g. a conditional that never happens), these parts should be erased, because they are not necessary to solve your problem.

1

If this is still at code level, I would certainly consider testing it. As you suggested, the usefulness of such a test could be:

  • to verify that the correct methods are being called
    • However, if you test the call to A/B with a mocker, you no longer treat non-atomic as a black box - is it important that those specific methods are called, as long as the resulting behavior is correct? If you refactor the underlying structure this test will break, even though the functionality might still work perfectly.
  • to verify that the method returns the correct output sum of both smaller methods (no erroneous logic happens inbetween)
    • you could indeed provide some sets of input and output to non-atomic. This seems fine, but if you ever change the behavior of A/B this test will also break. So, why not just call A/B yourself in the test (to set up the expected output) and compare this to the non-atomic output? This way, changes to A/B will automatically get reflected here. (Of course, this counters the first bullet argument - if you ever get rid of the A/B implementation this test will then break.)

Main point I'm trying to make: beware of exercising the same (underlying) code against tests of multiple layers. You might end up needing to fix plenty of tests for a simple change.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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