Unit Testing and Cyclomatic Complexity

Suppose you have a unit, A, which outputs subunits B and C. B and C both have two possible outputs. Is it better to a) have a test for the default output, a test for B’s alternative output, and a test for C’s alternative output, b) have another test for both B and C’s alternative outputs (since A is the unit under test, and it has four possible outputs), or c) treat B and C as separate units?

Assuming B and C (and D, E, etc.) are independent of each other, the first approach requires n + 1 tests, the second requires 2^n tests, and the third requires 2n + 1 tests.

I’m leaning toward the first approach. The second requires so many pointless, repetitive tests that the only sane approach is to write a utility that tests all the possible combinations automatically, and the third means breaking up units even when this doesn’t make conceptual sense.

1 Like

What do you mean by a unit that “outputs” subunits? A unit is typically a class or a module, something that can be isolated from other code in order to test it. Are you generating code or something?

There’s probably a better word for it. I mean the component parts of a unit that require testing. In my case, the unit is a React component, and the subunits are the conditional JSX elements it renders.

This is a simplified version:

const Sidebar = (props) => {

  const { user = {} } = props;

  //render

  return (
    <div>
      <p>{`Hi, ${user.name || "Anonymous"}!`}</p>
      <hr />
      <button>{user.auth ? "Logout" : "Login"}</button>
    </div>
  );

};

If defined, user has the following (simplified) schema:

{
  name: "",
  auth: {
    provider: "",
    id: ""
  }
}

I can think of four ways to test this:

  1. Test the default case and the case where user.name and user.auth are defined. This covers all branches and both common scenarios.
  2. Additionally test the case where only user.auth is defined. This covers all scenarios.
  3. Additionally test the case where only user.name is defined. This covers all paths.
  4. Test the p and button elements as separate components. This has the side benefit of greater reusability.

Your second post is much easier and simpler to understand, than your original, since it gives a pretty clear cut example.

I assume in this situation your really only testing 2 “end-user” scenarios:

  1. If the user is logged in
  2. If the user isn’t logged in

If your testing what happens if only name or auth is provided what do you do. I’m not sure if that situation is suppose to happen naturally right? If there is another case where you can have a user.name but no auth, then yes I’d test it.

I do recommend not overthinking testing this code too much. Test the situation you expect to account for and nothing more.

1 Like

I was trying to generalize. In this case, my hangup is that it’s valid for user.name to be undefined even if the user is logged in. That’s a distinct scenario, but the “default” case already covers that branch of the code.

It matters because it sets a precedent. If I don’t test it, I should rewrite my tests to make it clear that I’m testing branches. If I do test it, I should write new tests for all the other scenarios I’ve ignored.