Photo by Louis Reed on Unsplash
POJOs (Plain Old Java Objects) and their ilk such as value objects and JavaBeans occupy a unique place in modern J...
For further actions, you may consider blocking this person and/or reporting abuse
Nope.
Either you should have your ide generate your pojo methods, or use lombok. Or use kotlin.
In all cases testing pojos is stupid. Unless you want to test serialization or deserialization but then you are not just testing pojos.
The blind religion of "everything must be tested" is irrelevant in the professional world where developer time is money, and test code must produce sufficient value: if it doesn't, then you are wasting your time. And I also mean to imply that not all tests are created equal not all are equally valuable.
Why? What's the argument? Let's hear it.
You are insisting on testing a thing, that might belong to someone else, right? (client code assumption) well, you should test YOUR code, not 3rd party.
You stated that there might be additional logic in getter/setter. Or that there might be a bug in getter. This is serious claim, specifically in times when getters and setters are generated by IDE or directly the compiler (thanks to lombok).
I attempted to test getters and setters once. I did it with reflection, scanned for all of them and tested for primitives and boxed primitives. Goal was to drastically increase coverage stats and it proved to my coworker that there was no point for it. After that team agreed to remove POJO from coverage stats.
Actually, no. If "a thing" exists in your codebase, and that thing is a POJO, then it is being used by you, by some other thing in your codebase. Your code is the client code.
I would argue not removing POJO for test coverage stats. Because it may flesh out unused getters or setters that can be removed.
Even if let’s say that an accessor method is never directly referenced by your code, and is only access through some marshalling/unmarshalling of JSON or DB Rows, then that may just mean that you dont have enough test on the marshalling/unmarshalling logic
Focus. It's not about that it's referenced by marshalling, or reflection in general, but about not being a part of direct code base. They are generated by the compiler.
There are reasons for providing 70%-80% on code coverage, and agreement on not testing POJO is one of them.
I think that in general you have to rethink WHY people write tests, or even TDD, and why they they don't.
Also, what are the drawbacks of having 100% coverage.
Please note that coverage doesn't guarantee that your code will be bug free, and instead of focusing on scoring coolness points, focus on bugs coming from misuse of programs logic, faulty incoming data and concurrency.
Tests are meant to ensure behavior. Not data structure. However, most behaviour would have to work with some data structures.
One thing I learnt from TDD is that your POJOs test coverage are automatically covered by the logic that’s working against it.
(Gasp! That’s a design smell on itself which is why I hate POJOs. They pretend to be objects wherein in fact, they’re just data structures. Both OOP and Procedural practice loose coupling and tight cohesion principally. The main difference is what they consider to be tightly coupled. In procedural, data and behaviour are separated. That’s because there is no one owner of the data. In OOP, the data and the behaviour that acts on it needs to be in one Object. That way, you enforce abstraction and encapsulation because the data is just part of the implementation details.)
Are we using terminology correctly here?
I always understood "POJO" to mean "Plain old java object" and first saw it with the introduction of Spring where Services were just "POJOs" ie. they didn't inherit from some weird framework but used inversion of control to get other services injected. So POJO to me meant just plain old java objects without weird framework dependencies. (en.wikipedia.org/wiki/Plain_old_Ja...)
What is being discussed here is a "Value object" an object that just has getters/setters that stores data, and in that sense is nothing more than a glorified struct. (en.wikipedia.org/wiki/Value_object)
Am I getting my terminology wrong?
It's more that people commonly misuse the term POJO so that in common slang usage it really could mean any of POJO, JavaBean, value object, or more.
I always thought of value objects as immutable (no setters). A POJO can have setters. I think lombok's @Data and @Value provide good standards.
It may be a good practice for a value object to be immutable but it's not required.
POJO just means any Java object that doesn't depend on a weird framework.
Testing is about time optimization: you'll never have enough time to test everything, thus you spend your time on the highest value tests.
POJOs will never make it high enough in the priority list to bother testing directly. Because they are always part of some other use-case, the test for those use-cases transitively include the tests for the POJOs. In most projects, adding extra tests for POJOs provide no extra coverage, nor increase in quality -- errors in them will show up in the other tests.
Never say never 🙂
While I like your argument from principle, I still think it's missing a critical point. There's an even higher set of principles which can be applied which integrate several higher values and motivations such that it easily resolves the many explicit and implicit conflicts which appear within your post.
An implied conflict you're trying to resolve within your article is between time spent assuming a software component is sufficiently trustworthy versus demonstrating it's actually trustworthy with an (automatable) battery of tests. This same conflict occurs even more strongly in dynamically typed languages (like JavaScript and Python). And the ambiguity you point out is resolved by attempting to identify a threshold at which you ought test a POJO getter (and possibly also its setter mate[s]).
So, if we step back and talk about a couple of higher values, some of which you mention in passing, we can start to evaluate and appreciate other possibly more effective solution pathways. Pathways which clearly and succinctly identify precisely when, where, and how much to write the tests.
The first higher value is "willpower". It is the Software Engineer's (including any developer, programmer, data scientist, etc.) HIGHEST and MOST SCARCE value. It is also THE system constraint. And as we know from ToC (the Theory of Constraints by Eliyahu Goldratt), this means we must strive to subordinate ALL activities such that they maximize the value of the time spent on THE system constraint.
Willpower?!? Yes. It is the EXTREMELY SCARCE SINGLE THREADED resource utilized to evaluate and then produce both the POJO and the accompanying automated tests. So, every second ineffectively and/or inefficiently utilized, or worse, generates irritation, frustration, and/or resistance, is a precious second not being spent on other higher value activities. That second is gone forever. And, it is quite literally the basis of the reason you observe the seemingly irrational feuds about code formatting, the "best" code editor, the proper way to use "null, etc.. And that leads us into the second higher value...
Writing code for fast readability later to facilitate EASIER REASONING AND REFACTORING is the highest goal, and not for other much lower priority concerns (like fewer characters, fewer lines, fewer classes, some probably irrelevant execution optimization, some seemingly clever trick, etc.). As you mention in your article, the code is read many MANY more times than it is ever written. It follows, then, that we focus on optimizing the primary constraint (willpower) of coders that follow us later. And that is their reading AND RAPID UNDERSTANDING of said code
Once you understand and appreciate why these two values ought to be amongst the highest values when evaluating which software engineering principles to use, the POJO testing discussion magically transforms into a whole different exploration. And then finding the "where to test threshold" becomes an easy call. It also vastly reduces the resistance to producing needed POJOs because the testing burden and maintenance is substantially reduced (if not eliminated.
So, how do we translate that into actual thinking and action? What are some examples which are derived from and illuminate the effective use of these new higher value distinctions?
Oops, I've run out of time. If you're at all curious, hit me up (jim dot oflaherty dot jr at gmail dot com) and let me know. I'd be happy to write you back about it. Even here as another comment, if you like. It's too much work to write more without knowing it is anticipated.
BTW, I'm deeply passionate about software engineering, psychology, and ToC, just in case that did't come through loud enough, LOL!
"From that lens, you should even test getters. They may simply be returning a value, but what if they're not? Or, what if they are, but that changes in the future?"
I don't really get the "what if" part - you write tests to cover implementation of your code, there's no "what if", you know what your getter does because you just wrote it!
Writing a test for a getter just because it might, in the future, change implementation to ad-hoc computation looks to me like an example of violation of YAGNI (you ain't gonna need it) and future coding - yes, everything might change logic in the future. But I bet that 99 (or even 99.9%) of getters you wrote through your career never did. And those that did probably changed so much that the test you would have wrote in the past would became obsolete and completely rewritten anyway.
Also nothing in software happens by accident. In the future the implementation will change if and only if someone comes to that getter code and makes a change. If that changes implementation from trivial to non-trivial it's that someone who should write a test, not some random developer who generated trivial POJO 5 years ago.
If I read you correctly, your logic is that since you know what your getter does you don't need to test it. That's the same logic that people use to not test any of their code. "I know what the code does. I don't need to test it."
Tests prove that your code does what you think it does, though.
Another question I have is can you guarantee that if anyone else is going to touch that code they'll write a test for it? In a shared codebase, that's a nice idea, but not the practical outcome usually. Especially if there's no code coverage requirement on that package, so you'll see people neglect adding any tests there.
I get your point about tests prove your code does what you think it does, but as always common sense applies - you already mentioned one of the arguments I would make here "Why test that the language does what the language does?". I would always write a test for any method that consists of at least one actual operation, so method like
getFullName() { return firstName + " " + lastName }
would always warrant aassertEquals("John Smith", new Person("John", "Smith").getFullName())
test.Also earlier you made a strong point about testing constructor contract, I obviously agree that sloppy developer could write this code:
where he clearly thinks his trivial code that doesn't need test does something else than what it actually does. A trivial
assertEquals("Smith", new Person("John", "Smith").getLastName())
test would catch it.But is that enough to convince me to write these tests? Not really, for 2 reasons:
entity classes are never mocked away meaning some other test will catch this anyway. If none of your test catches it, you probably have way more serious problems in your code base than untested POJOs so you can probably spend your precious time fixing these more serious problems first. Once you do, you no longer need POJO tests because now none of your entities have unused fields anymore and all services operating on these POJOs have at least their happy paths tested meaning all your getters and setters should either be covered or not exist.
any sane Java developer would generate their constructor, setters and getters for data classes. If I advocated in my team to write test to POJOs it would probably drive everyone crazy - "I just generated this 8 args constructor and 8 getters using just 2 keypresses, do you really want me now to write a test and then copy paste it 7 times with small modifications?". On the other hand if a developer doesn't generate their setters and getters he will probably spend his time way more productively learning and actually start using tools to do that, than writing these tests. Using generators will increase their productivity forever. Writing tests won't.
As for
First thing to say is of course not, just as you can't guarantee that anyone else is not going to delete your tests. We can't guarantee much about behaviour of future devs, but there has to be some basic trust in organization. We shouldn't write code to protect from future misbehaving developers. What we should do though is minimize the risk of someone misbehaving by scrutinizing each change made. In my organization for instance we have following process:
publish-cr
, if you have time to rungit push
you also have time to runpublish-cr
. So there's no reason to not do that (barring code review tool failure, perhaps)And to aid in detecting missing tests our CR tool is quite sophisticated - when change is published, it displays diff side by side, runs tests with coverage and on that side-by-side diff marks in light red lines not covered by any test. Engineers doing CR would usually raise a concern if you made a non-trivial change on a red line. Unless common sense says this line doesn't really need test, and if common sense of both author and reviewer says that - then yes, you probably do not need test.
So no, I can't guarantee future dev will write test, but with proper tooling and processes in place I can minimize the risk of having crappy code reaching production. IMHO this is way more important than any kind of artificial rules "at least 80% coverage" or "all new code has to be covered by test". To me common sense always wins against dogma. I witnessed too many changes which covered 100% lines, including all their
catch
blocks to reach coverage goal but failed to write a basic "Serialize/Deserialize` test ("if I serialize this object to DB and then read it back, is it equal to what I wrote?") to believe in threshold-based rulesAnyway, I understand your position and don't see anything particular wrong with it. Just wanted to share my 3 cents.
they are confusing "value object" with POJO it seems.
Thankfully now we have data classes in Kotlin and don't have to bother with nullability annotations or accidental NPE or writing equals()/hashCode() anymore.
Just don't, seriously.
Just don't what?