Channels ▼
RSS

Testing

Saying No To Properties


Unfortunately, this is the case with virtually every contract that relies on a property. The class contract becomes unenforceable. The only way to fix the problem is to completely hide the type of the Value object, but there's no way to do that if we expose the object to the outside world. Value must be be completely encapsulated inside the object. (It should be private with no public accessor or mutator.)

To drive the point home, having a Value without a currency isn't of much use. If you're thinking in terms of properties, however, it's tempting to do something like the following to add the notion of currency:

    class Money
    {
        public decimal      Value    { get; set;}
        public CurrencyCode Currency { get; private set;}
        //...
    }

(I'm not permitting modifications to the currency through the property because you need to pass arguments — like a target currency — to do that).

The amount of work this simple change requires is simply unacceptable. For example, your original code might calculate a simple sum: total.Value += item.Value; Once we introduce the Currency property, however, we need to change that simple statement to something like the following:

    total.Value += item.Value * 
                CurrencyCoverter.getExchangeRate(
                                total.Currency, lineItem.cost.Currency);

The new code is ugly, but more significantly, we have to make a similar change everywhere we add together two units of Money. That could add up to many hundreds, or even thousands, of changes scattered throughout the code, and we'll almost certainly miss a few, thereby introducing many new bugs. Automatic refactoring won't do this work for us.

You could argue that you can do the currency conversion in the Value setter, which is definitely a move in the right direction. I'd wager that a better approach is to give up on the properties entirely and completely hide the way that the value is represented from the outside world.

Here's what I'd do. Let's first rewrite the simplistic example to hide implementation:

    class Money
    {   private readonly float value;

        public Money ( float value )
        {   this.value = value;
        }
        public Money plus( Money operand )
        {   return new Money(value + operand.value);
        }
    }

I can now update the total like this:

Money total;
Money item;
//...
total = total.plus( item );

Of course, I could use operator overloading to make it look like this:

total += item; 

but I'll keep the example simple.

The first thing to notice is that full encapsulation has made the client code simpler (and shorter), and that alone is a good reason to use this approach. You will (hopefully) use Money more often than you define it, so encapsulation can result in considerable reduction in code size, along with the concomitant reduction in development time and debugging cost. It takes a bit longer to write the class, but it takes a lot less time to write the code that uses the class.

Let's see how this approach holds up to change. What if I want to change the internal representation of value to decimal? Here's the new class:

    class Money
    {   private readonly decimal value;

        public Money ( decimal value )
        {   this.value = value;
        }

        // For backwards compatibility
        public Money ( float value ) : this( (decimal)value )

        public Money plus( Money operand )
        {   return new Money(value + operand.value);
        }
    }

Note that the code to compute the sum hasn't changed at all. It still looks like this: total = total.plus( item ); That point is the essential one. I can change the implementation without changing any of the code that uses the original implementation.

To drive in my point, let's add the notion of currency:

    class Money
    {
        private readonly decimal      value;
        private readonly CurrencyCode currency;

        public Money ( decimal value, CurrencyCode currency = CurrencyCode.USD )
        {   this.value = value;
            this.currency = currency;
        }

        //...

        public Money plus( Money operand )
        {
            decimal newValue == value +
                (operand.value *
                    CurrencyCoverter.getExchangeRate(currency, operand.currency));

            return new Money(newValue, currency );
        }
    }

The code that does the sum has still not changed: total = total.plus( item );

I hope you're getting the idea. By fully encapsulating the implementation of Money — eliminating the properties entirely — I end up with an implementation that I can change radically without changing any of the code that uses the Money class.

This sort of flexibility is much more difficult to achieve if you use properties.

What about testability? The fact that the client code isn't changing gives us a test advantage. In general, it's a bad idea to change both your test and the class that you're testing at the same time. If you do, you're never sure whether a bug is in the test or in the class. In a regression-test environment, you don't really want to change your test code at all. (That's not to say that a test doesn't have a lifetime. In an Agile world, it's possible for a class to have to change enough that the old tests simply aren't useful any more, in which case they should be discarded.) With properties, though, you'll certainly have to rewrite your tests every time a property changes type (or you add or remove one).

Properties also add unnecessary coupling relationships between classes, which also hurts testability. For example, if you use a property, you need to test not only the object you're interested in, but also all the objects whose properties are accessed and the interactions between the entire system of interlocking objects. That makes it very difficult to write a unit test — or more accurately, your smallest "unit" becomes a system of interrelated classes rather than a single class.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video