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

有帮助吗?

解决方案

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).

其他提示

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.

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.

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.

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.

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.

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.

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.

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().

许可以下: CC-BY-SA归因
scroll top