Question

I often use enum types in my code with a switch to apply logic to each type. In these cases it's important that each enum has code implemented.

For example;

public enum eERROR
{
    REQUIRED,
    DUPLICATE,
    UNKNOWN,
    MISSING_VALUE
}

public void Error(eERROR pError)
{
    switch (pError)
    {
        case eERROR.REQUIRED:
            // ......
            return;
        case eERROR.DUPLICATE:
            // ......
            return;
        case eERROR.MISSING_VALUE:
            // ......
            return;
        case eERROR.UNKNOWN:
            // ......
            return;
    }

    throw new InvalidArgumentException("Unsupported error type");
}

At the bottom I've added an exception as a last resort check that a programmer remembered to add any new enum types to the switch statement. If a new type is added to eERROR the exception could be thrown if the code is not updated.

Here is my problem.

The above code generates unit test coverage of only 99% because I can not trigger the exception. I could add a generic unhandled to eERROR and call Error(eERROR.unhandled) just to get 100% coverage, but that feels like a hack to solve something that is not a problem just to get full coverage. I could remove the line but then I don't have the safety check.

Why is 99% a problem? because the code base is so large that any uncovered method doesn't move coverage to 98%. So I always see 99% and don't know if I missed a method or two.

How can I rewrite Error() so that all newly added enums that are not added to the switch will be caught somehow by the programmer, and also be covered by tests. Is there a way of doing it without added a dummy enum?

Was it helpful?

Solution

Depending on your language, you should just be able to pass in a garbage value to the Error constructor. Most languages just use integers for enum values. In pseudo-code:

e = new Error(eError -1)
>> InvalidArgumentException: Unsupported error type

OTHER TIPS

As a developer who applies test driven development from day to day I believe the question should be the other way around. When you cannot test the code there is no reason for that code. You may extend the enum by inheritance (if possible) or use a mock instead to trigger a specific case.

You cannot test code that is intended to be run only in the event of a programming error, because that would imply that your test suite can only run successfully if there is a programming error in the code base! The fact that it's nevertheless useful to write such guards against supposedly impossible cases is one of the reasons why the goal "absolutely 100% code coverage" is not a good goal.

One question do you need the catch all clause at all? If a new element is added to the Enum will the type checker catch that for you? I know that in Erlang if you wrote something like that case statement then dialyzer would tell you flat out that there is no way that the error case could ever match via static analysis .

Maybe I am just spoiled by very good tools that don't exist in all languages.

What about this, then you can test with null as invalid value?

public void Error(eERROR pError)
{
    if (pError != null) {
        switch (pError)
        {
            // ......
        }
    }

    throw new InvalidArgumentException("Unsupported error type: " + pError);
}
Licensed under: CC-BY-SA with attribution
scroll top