4

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?

  • 2
    At least in C# you can trigger it: Error((eERROR)-1). .net enums are just fancy integers, any value is valid, even those without name. Your code looks more like java, but you didn't specify a language. – CodesInChaos Jan 29 '14 at 16:06
  • 4
    IMO the best solution is telling the code coverage tool that the branch is designed to be unreachable. – CodesInChaos Jan 29 '14 at 16:10
  • @CodesInChaos I'm using Resharper with DotCover. I don't know if it has a feature to do that. – Reactgular Jan 29 '14 at 16:11
  • Duplicate on stackoverflow: How to test or exclude private unreachable code from code coverage – CodesInChaos Jan 29 '14 at 16:13
  • @CodesInChaos: I don't consider this a duplicate. The question you linked to is asking about testing a private function, which has different rules when it comes to testing. – Darrick Herwehe Jan 29 '14 at 16:53
4

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
6

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.

  • false. See above answer to know how to test it. – Tom Roggero Feb 4 '16 at 21:14
  • 1
    @TomRoggero Please specify the author of the answer to identify the answer. The order of answers may change overtime. – Tarun Maganti Mar 31 '17 at 6:59
1

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.

  • Do you think this kind of code is good or bad? Should it throw the exception or should it not? – Reactgular Jan 29 '14 at 16:10
  • 3
    @MathewFoscarini I think it's good. In fact, I write all my switches with a return in each case and a throw after the block, so if I ever forget to add a new case, I'll get compile-time and run-time trouble. – Kilian Foth Jan 29 '14 at 16:12
  • When writing a library, you generally want a test in there for what happens if the library gets used the wrong way (to make sure it fails in an expected way, rather than causing undefined behavior). So in this case it depends on the language: Something other than the 4 enums listed might be able to be passed in due to typecasting or monkey-patching. If so, then yes, you can "test code that can only be run in the event of a programming error". – Izkata Jan 30 '14 at 3:25
1

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 happens in Erlang when you add another enum element? – Cephalopod Jan 29 '14 at 19:44
  • Well Erlang does not have an enum per se, but you can have unions of atoms. If you add a new element to a union if it won't match dialyzer, which is a static analysis tool, will tell you that it can't match – Zachary K Jan 30 '14 at 4:15
  • So basically, if you add an element the analyzer will tell you to fix the Error method? What happens when the enum comes from a libary and gets a new element with the next release, but the method in question is not updated yet? I'm asking, because that is the reason why the static analyzer of Java forces you to cover the default case, even if it seems "impossible" to reach. – Cephalopod Jan 30 '14 at 9:05
  • Then when you update the library, dialyzer will update its lookup table and let you know. Mind you it is possible to have a wildcard case clause that might catch these, but in general Erlang discourages defensive programming. – Zachary K Jan 30 '14 at 12:59
0

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);
}

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.