Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should unit tests be written for getter and setters?

Tags:

unit-testing

People also ask

When should a unit test be written?

For Test-Driven Development (TDD), you write unit tests before writing any implementation. This makes your implementation details in your code shorter and easier to understand. In this instance, the best time to write unit tests is immediately. For others, most developers write unit tests after the code's been written.

Are getters and setters bad practice?

Getter and setter methods (also known as accessors) are dangerous for the same reason that public fields are dangerous: They provide external access to implementation details. What if you need to change the accessed field's type? You also have to change the accessor's return type.

Should I aim 100% test coverage?

You should aim for executing 100% of the code before your customer does and 100% automation in that process. Whether the coverage tool will recognize that is irrelevant. Test coverage serves as one of the great lightning rods in the world of software development.


I would say no.

@Will said you should aim for 100% code coverage, but in my opinion that's a dangerous distraction. You can write unit tests that have 100% coverage, and yet test absolutely nothing.

Unit tests are there to test the behaviour of your code, in an expressive and meaningful way, and getters/setters are only a means to an end. If you tests use the getters/setters to achieve their goal of testing the "real" functionality, then that's good enough.

If, on the other hand, your getters and setters do more than just get and set (i.e. they're properly complex methods), then yes, they should be tested. But don't write a unit test case just to test a getter or setters, that's a waste of time.


Roy Osherove in his famous book 'The Art Of Unit Testing' says:

Properties (getters/setters in Java) are good examples of code that usually doesn’t contain any logic, and doesn’t require testing. But watch out: once you add any check inside the property, you’ll want to make sure that logic is being tested.


A resounding YES with TDD


Note: This answer keeps getting upvotes, albeit potentially a bad advice. To understand why, have a look at its little sister below.


Controversial alright, but I'd argue that anyone who answers 'no' to this question is missing a fundamental concept of TDD.

For me, the answer is a resounding yes if you follow TDD. If you aren't then no is a plausible answer.

The DDD in TDD

TDD is often quoted as having thee main benefits.

  • Defence
    • Ensuring the code may change but not its behaviour.
    • This allows the ever so important practice of refactoring.
    • You gain this TDD or not.
  • Design
    • You specify what something should do, how it should behaves before implementing it.
    • This often means more informed implementation decisions.
  • Documentation
    • The test suite should serve as the specification (requirements) documentation.
    • Using tests for such purpose mean that the documentation and implementation are always in consistent state - a change to one means a change to other. Compare with keeping requirements and design on separate word document.

Separate responsibility from implementation

As programmers, it is terribly tempting to think of attributes as something of significance and getters and setter as some sort of overhead.

But attributes are an implementation detail, while setters and getters are the contractual interface that actually make programs work.

It is far more important to spell that an object should:

Allow its clients to change its state

and

Allow its clients to query its state

then how this state is actually stored (for which an attribute is the most common, but not the only way).

A test such as

(The Painter class) should store the provided colour

is important for the documentation part of TDD.

The fact that the eventual implementation is trivial (attribute) and carries no defence benefit should be unknown to you when you write the test.

The lack of round-trip engineering...

One of the key problems in the system development world is the lack of round-trip engineering1 - the development process of a system is fragmented into disjointed sub-processes the artifacts of which (documentation, code) are often inconsistent.

1Brodie, Michael L. "John Mylopoulos: sewing seeds of conceptual modelling." Conceptual Modeling: Foundations and Applications. Springer Berlin Heidelberg, 2009. 1-9.

...and how TDD solves it

It is the documentation part of TDD that ensures that the specifications of the system and its code are always consistent.

Design first, implement later

Within TDD we write failing acceptance test first, only then write the code that let them pass.

Within the higher-level BDD, we write scenarios first, then make them pass.

Why should you exclude setters and getter?

In theory, it is perfectly possible within TDD for one person to write the test, and another one to implement the code that makes it pass.

So ask yourself:

Should the person writing the tests for a class mention getters and setter.

Since getters and setters are a public interface to a class, the answer is obviously yes, or there will be no way to set or query the state of an object. However, the way to do this is not necessarily by testing each method in isolation, see my other answer for more.

Obviously, if you write the code first, the answer may not be so clearcut.


tl;dr: Yes you should, and with OpenPojo it's trivial.

  1. You should be doing some validation in your getters and setters so you should be testing that. For example, setMom(Person p) should not allow setting anyone younger than themselves as their mother.

  2. Even if you aren't doing any of that now, odds are you will in the future, then this will be a good for regression analysis. If you want to allow setting mothers to null you should have a test for that should someone change that later on, this will reinforce your assumptions.

  3. A common bug is void setFoo( Object foo ){ foo = foo; } where it should be void setFoo( Object foo ){ this.foo = foo; }. (In the first case the foo that is being written to is the parameter not the foo field on the object).

  4. If you are returning an array or collection you should be testing whether or not the getter is going to be performing defensive copies of the data passed into the setter before returning.

  5. Otherwise, if you have the most basic setters/getters then unit-testing them will add maybe about 10 minutes at most per-object, so what is the loss? If you add behaviour you already have a skeleton test and you get this regression testing for free. If you are using Java, you have no excuse since there is OpenPojo. There are an existing set of rules you can enable and then scan your entire project with them to make sure they are applied consistently within your code.

From their examples:

final Validator pojoValidator = ValidatorBuilder.create()
        .with(
            new NoPublicFieldsRule  (),
            new NoPrimitivesRule    (),
            new GetterMustExistRule (),
            new SetterMustExistRule ()
        )
        .with(
            new DefaultValuesNullTester (),
            new SetterTester            (),
            new GetterTester            ()
        )
        .build();


pojoValidator.validate(  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  );

Yes, but not always in isolation

Allow me elaborate:

What is a unit test?

From Working effectively with legacy code1:

The term unit test has a long history in software development. Common to most conceptions of unit tests is the idea that they are tests in isolation of individual components of software. What are components? The definition varies, but in unit testing, we are usually concerned with the most atomic behavioral units of a system. In procedural code, the units are often functions. In object oriented code, the units are classes.

Note that with OOP, where you find getters and setters, the unit is the class, not necessarily individual methods.

What is a good test?

All requirements and tests follow the form of Hoare logic:

{P} C {Q}

Where:

  • {P} is the precondition (given)
  • C is the trigger condition (when)
  • {Q} is the postcondition (then)

Then comes the maxim:

Test behaviour, not implementation

This means that you shouldn't test how C achieves the post-condition, you should check that {Q} is the result of C.

When it comes to OOP, C is a class. So you shouldn't test internal effects, only external effects.

Why not test bean getters and setters in isolation

Getters and setters may involve some logic, but so long this logic does not have external effect - making them bean accessors2) a test will have to look inside the object and by that not only violate encapsulation but also test for implementation.

So you shouldn't test bean getters and setters in isolation. This is bad:

Describe 'LineItem class'

    Describe 'setVAT()'

        it 'should store the VAT rate'

            lineItem = new LineItem()
            lineItem.setVAT( 0.5 )
            expect( lineItem.vat ).toBe( 0.5 )

Although if setVAT would throw an exception, a corresponding test would be appropriate since now there is an external effect.

How should you test getters and setters?

There is virtually no point changing the internal state of an object if such change has no effect on the outside, even if such effect comes later on.

So a test for setters and getters should be concerned with the external effect of these methods, not the internal ones.

For example:

Describe 'LineItem class'

    Describe 'getGross()'

        it 'should return the net time the VAT'

            lineItem = new LineItem()
            lineItem.setNet( 100 )
            lineItem.setVAT( 0.5 )
            expect( lineItem.getGross() ).toBe( 150 )

You may think to yourself:

Wait a sec, we are testing getGross() here not setVAT().

But if setVAT() malfunction that test should fail all the same.

1Feathers, M., 2004. Working effectively with legacy code. Prentice Hall Professional.

2Martin, R.C., 2009. Clean code: a handbook of agile software craftsmanship. Pearson Education.


While there are justified reasons for Properties, there's a common Object Oriented Design belief that exposing member state via Properties is bad design. Robert Martin's article on the Open Closed Principle expands upon this by stating that Properties encourage coupling and therefore limit the ability to close a class from modification -- if you modify the property, all consumers of the class will need to change as well. He qualifies that exposing member variables isn't necessarily bad design, it might just be poor style. However, if properties are read-only, there's less chance of abuse and side-effects.

The best approach I can provide for unit testing (and this may seem odd) is to make as many properties as possible protected or internal. This will prevent coupling while discouraging writing silly tests for getters and setters.

There are obvious reasons where read/write Properties should be used, such as ViewModel properties that are bound to input fields, etc.

More practically, unit tests should drive functionality through public methods. If the code you're testing happens to use those properties, you get code-coverage for free. If it turns out that these properties never get highlighted by code-coverage there's a very strong possibility that:

  1. You are missing tests that indirectly use the properties
  2. The properties are unused

If you write tests for getters and setters, you get a false sense of coverage and will not be able to determine if the properties are actually used by functional behavior.


If the cyclomatic complexity of the getter and/or setter is 1 (which they usually are), then the answer is no, you shouldn't.

So unless you have a SLA that requires 100% code-coverage, don't bother, and focus on testing the important aspect of your software.

P.S. Remember to differentiate getters and setters, even in languages like C# where properties might seem like the same thing. The setter complexity can be higher than the getter, and thus validate a unit-test.