15

If you have an enum with values only (no methods as one could do in Java), and this enum is part of the business definition of the system, should one write unit tests for it?

I was thinking that they should be written, even if they could seem simple and redundant I consider that what concerns the business specification should be made explicitly written in a test, whether it be with unit/integration/ui/etc. tests or by using the type system of the language as a testing method. Since the values that an enum (e.g. in Java) must have, from the point of view of the business, cannot be tested using the type system I think there should be a unit test for that.

This question isn't similar to this one since it doesn't address the same problem as mine. In that question there is a business function (savePeople) and the person is inquiring about the internal implementation (forEach). In there, there's a middle business layer (the function save people) encapsulating the language construct (forEach). Here the language construct (enum) is the one used to specify the behavior from a business standpoint.

In this case the implementation detail coincides with the "true nature" of the data, that is: a set (in the mathematical sense) of values. You could arguably use an immutable set, but the same values should still be present there. If you use an array the same thing must be done to test the business logic. I think the conundrum here is the fact that the language construct coincides very well with the nature of the data. I am not sure if I've explained myself correctly

  • 19
    What precisely would a unit test of an enum look like? – jonrsharpe Sep 27 '17 at 13:52
  • 13
  • @jonrsharpe It would assert that the values that are inside the enum are those that you expect. I would do it by iterating over the values of the enum, adding them to a set, for example as strings. Order that set. The compare that set against an ordered list of values written by hand in the test. They should match. – IS1_SO Sep 27 '17 at 13:56
  • 1
    @jonrsharpe, I like to think of unit tests also as "definitions" or "requirements" written in code. A unit test of an enum would be as simple as checking the number of items on the enum and their values. Specially in C#, where the enums are not classes, but can be directly mapped to integers, guaranteeing their values and not programming by coincidence may prove useful for serialization purposes. – Machado Sep 28 '17 at 13:19
  • 2
    IMHO it's not much more useful than testing if 2+2=4 in order to check if the Universe is right. You test the code using that enum, not the enum itself. – Agent_L Sep 28 '17 at 16:03
39

If you have an enum with values only (no methods as one could do in Java), and this enum is part of the business definition of the system, should one write unit tests for it?

No, they are just state.

Fundamentally, the fact that you are using an enum is an implementation detail; that's the sort of thing that you might want to be able to refactor into a different design.

Testing enums for completeness is analogous to testing that all of the representable integers are present.

Testing the behaviors that the enumerations support, however, is a good idea. In other words, if you start from a passing test suite, and comment out any single enum value, then at least one test should fail (compilation errors being considered failures).

  • 5
    But in this case the implementation detail coincides with the "true nature" of the data, that is: a set (in the mathematical sense) of values. You could arguably use an immutable set, but the same values should still be present there. If you use an array the same thing must be done to test the business logic. I think the conundrum here is the fact that the language construct coincides very well with the nature of the data. I am not sure if I've explained myself correctly. – IS1_SO Sep 27 '17 at 14:32
  • 4
    @IS1_SO — to VOU's point that one test should fail though: did it? In which case, you didn't need to test the Enum specifically. Did it not? Maybe that's a sign that you could model your code more simply and create an abstraction over the 'true nature' of the data — e.g. regardless of the cards in a deck, do you really need to have an representation of [Hearts, Spades, Diamonds,Clubs] if you only ever card if a card is red/black? – anotherdave Sep 27 '17 at 20:05
  • 1
    @IS1_SO lets say you have an enum of error codes, and you want to throw a null_ptr error. Now that has an error code via the enum. The code checking for a null_ptr error looks up the code via the enum too. So it may have a value of 5 (for ex). Now you need to add another error code. The enum is changed (lets say we add a new one to the top of the enum) The value of null_ptr is now 6. Is this a problem? you now return an error code of 6 and test for 6. As long as everything is logically consistent, you are fine, despite this change breaking your theoretical test. – Baldrickk Sep 28 '17 at 16:09
17

You don't test an enum declaration. You may test whether function input/output has the expected enum values. Example:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

You don't write tests verifying then enum Parity defines the the names Even and Odd. Such a test would be pointless since you would just be repeating what is already stated by the code. Saying the same thing twice does not make it more correct.

You do write tests verifying GetParity say will return Even for 0, Odd for 1 and so on. This is valuable because you are not repeating the code, you are verifying the behavior of the code, independent of the implementation. If the code inside GetParity were completely rewritten, the tests would still be valid. Indeed the major benefits of unit tests is they give you freedom to rewrite and refactor code safely, by ensuring the code still works as expected.

But if you have a test which ensures an enum declaration defines the expected names, then any change you make to the enum in the future will require you to change the test also. This means it is not just twice as much work, it also mean any benefit to the unit test is lost. If you have to change code and test at the same time, then there is no safeguard against introducing bugs.

  • I have updated my question in an effort to address this answer, check it out to see if that helps. – IS1_SO Sep 27 '17 at 15:03
  • @IS1_SO: OK that confuses me - are you dynamically generating the enum values, or what is happening? – JacquesB Sep 27 '17 at 15:05
  • No. What I meant is that in this case the language construct selected to represent the values is an enum. But as we know that is an implementation detail. What happens if one selects an array, or a Set<> (in Java) or a string with some separation tokens to represent the values? If that is the case then it would make sense to test that the values contained are the ones of interest to the business. That is my point. Does this explanation help? – IS1_SO Sep 27 '17 at 15:10
  • 3
    @IS1_SO: Are you talking about testing an enum instance returned from a function has a certain expected value? Because yes you could test that. You just don't need to test the enum declaration itself. – JacquesB Sep 27 '17 at 15:19
11

If there's a risk that changing the enum will break your code then sure, anything with the [Flags] attribute in C# would be a good case because adding a value between 2 and 4 (3) would be a bitwise 1 and 2 rather than a discreet item.

It's a layer of protection.

You should consider having an enum code of practice which all developers are familiar with. Don't rely on textual representations of the enum is a common one, but this might conflict with your serialisation guidelines.

I've seen people "correct" the capitalisation of enum entries, sort them alphabetically or by some other logical grouping all of which broke other bits of bad code.

  • 5
    If the numerical values of the enum are used anywhere, for instance when stored in a database, then reordering (including removing or inserting before the last value) could cause existing records to become invalid. – stannius Sep 27 '17 at 18:37
  • 3
    +1, this answer is underrated. If your enums are either part of serialization, input interface with external word or bitwise composable information, they'll definitely need to be tested for consistency at each version of the system. At least if you're worried about backwards compatibility, which usually is a good thing. – Machado Sep 28 '17 at 13:13
11

No, a test checking that a enum contains all the valid values and nothing more is essentially repeating the enum’s declaration. You would only be testing that the language properly implements the enum construct which is a senseless test.

That being said, you should test the behavior that depends on the enum values. For example, if you are using the enum values to serialize entities to json or whatever, or storing the values in a database, you should test the behavior for all the values of the enum. That way, if the enum is modified at least one of the tests should fail. In any case, what you would be testing is the behavior around your enum, not the enum declaration itself.

3

Your code should be working correctly independent of the actual values of an enum. If that is the case, then no unit tests are needed.

But you may have code where changing an enum value will break things. For example, if an enum value is stored in an external file, and after changing the enum value reading the external file will give the wrong result. In that case you will have a BIG comment near the enum warning anyone not to modify any values, and you may very well write a unit test that checks the numerical values.

1

In general, just checking that an enum has a hard-coded list of values is not of much value, as other answers said, because then you just need to update test and enum together.

I once had a case that one module used enums types from two other modules and mapped between them. (One of the enums had additional logic with it, the other one was for DB access, both had dependencies which should be isolated from each other.)

In this case, I added a test (in the mapping module) which verified that all enum entries in the source enum also exist in the target enum (and thus, that the mapping would always work). (For some cases I also checked the other way around.)

This way, when someone added an enum entry to one of the enums and forgot to add the corresponding entry to the other one, a test started to fail.

1

Enums are simply finite types, with custom (hopefully meaningful) names. An enum might only have one value, like void which contains only null (some languages call this unit, and use the name void for an enum with no elements!). It may have two values, like bool which has false and true. It may have three, like colourChannel with red, green and blue. And so on.

If two enums have the same number of values, then they're "isomorphic"; i.e. if we switch out all of the names systematically then we can use one in place of another and our program will not behave any differently. In particular, our tests will not behave any differently!

For example, result containing win/lose/draw is isomorphic to the above colourChannel, since we can replace e.g. colourChannel with result, red with win, green with lose and blue with draw, and as long as we do so everywhere (producers and consumers, parsers and serialisers, database entries, log files, etc.) then there will be no change in our program. Any "colourChannel tests" we wrote will still pass, even though there is no colourChannel any more!

Also, if an enum contains more than one value, we can always rearrange those values to get a new enum with the same number of values. Since the number of values hasn't changed, the new arrangement is isomorphic to the old one, and hence we could switch out all the names and our tests would still pass (note that we can't just switch out the definition; we must still switch out all use sites as well).

What this means is that, as far as the machine is concerned, enums are "distinguishable names" and nothing else. The only thing we can do with an enum is to branch on whether two values are the same (e.g. red/red) or different (e.g. red/blue). So that's the only thing that a 'unit test' can do, e.g.

(  red == red  ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
(  red != green) || throw TestFailure;
(  red != blue ) || throw TestFailure;
...

As @jesm00 says, such a test is checking the language implementation rather than your program. These tests are never a good idea: even if you don't trust the language implementation, you should test it from the outside, since it can't be trusted to run the tests correctly!

So that's the theory; what about the practice? The main issue with this characterisation of enums is that 'real world' programs are rarely self-contained: we have legacy versions, remote/embedded deployments, historical data, backups, live databases, etc. so we can never really 'switch out' all occurrences of a name without missing some uses.

Yet such things are not the 'responsibility' of the enum itself: changing an enum might break communication with a remote system, but conversely we might fix such a problem by changing an enum!

In such scenarios, the enum is a red-herring: what if one system needs it to be this way, and another needs it to be that way? It can't be both, no matter how many tests we write! The real culprit here is the input/output interface, which should produce/consume well defined formats rather than "whatever integer the interpret picks". So the real solution is to test the i/o interfaces: with unit tests to check that it's parsing/printing the expected format, and with integration tests to check that the format is actually accepted by the other side.

We may still wonder whether the enum is being 'exercised thoroughly enough', but in this case the enum is again a red herring. What we're actually concerned about is the test suite itself. We can gain confidence here in a couple of ways:

  • Code coverage can tell us if the variety of enum values coming from the test suite are enough to trigger the various branches in the code. If not, we can add tests which trigger the uncovered branches, or generate a wider variety of enums in the existing tests.
  • Property checking can tell us if the variety of branches in the code is enough to handle the runtime possibilities. For example, if the code only handles red, and we only test with red, then we have 100% coverage. A property checker will (try to) generate counterexamples to our assertions, such as generating the green and blue values we forgot to test.
  • Mutation testing can tell us whether our assertions actually check the enum, rather than just following the branches and ignoring their differences.
1

No. Unit tests are for testing units.

In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.

https://en.wikipedia.org/wiki/Unit_testing

An automated test for a declared enum would be testing the integrity of the language and the platform on which it's running rather than logic in code authored by the developer. It would serve no useful purpose - documentation included since the code declaring the enum serves as documentation just as well as code that would test it.

0

You are expected to test the observable behavior of your code, the effects of method/function calls on observable state. As long as the code does the right thing you are fine, you do not need to test anything else.

You do not need to assert explicitly that an enum type have the entries you expect, just like you do not assert explicitly that a class actually exists or that it has the methods and attributes you expect.

Actually by testing behavior you are implicitly asserting that the classes, methods and values involved in the test do exist, thus you do not need to assert it explicitly.

Note that you do not need meaningful names for your code to do the right thing, that's just a convenience for people reading your code. You could make your code work with enum values like foo, bar... and methods like frobnicate().

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.