Encapsulation vs TDD

241 views
Skip to first unread message

Dariusz Gafka

unread,
Sep 22, 2016, 3:09:04 AM9/22/16
to Growing Object-Oriented Software
Hey,

Fundamental to OOP is communication between objects. Objects communicating (messaging) via public contracts they expose.
And as much as I understood GOOS, this what we should focus on while doing TDD.

What about situations when there is no communication, and we want to test single object? 

Let's take as a example User.

User
{
    public void changeEmail (Email) ;
}

There is no requirement from any object to retrieve email, so there is no "getEmail()" method.

As we lack of getter for email, we are not able to assert it. 
This goes for all "command" like methods, which doesn't have opposite "queries".

Of course there will be test on upper abstraction level, which will validate calling changeEmail. But still we don't have proof, that the email was for real changed. 

Exposing "getEmail", would make us expose public interface only for testing purposes, which feels wrong.
Not exposing internal state of object, will for sure make our tests less sensitive to changes. But we are not sure about correctness of the system.




Matteo Vaccari

unread,
Sep 22, 2016, 3:41:43 AM9/22/16
to growing-object-o...@googlegroups.com
Hi Dariusz,

It seems to me that if someone calls user.changeEmail(...), there will be, in the end, an observable effect somewhere.  Because if there's no observable effect, then why is the changeEmail() method there in the first place?

So if there's an observable effect, then I would look for a way to check that after user.changeEmail(...), that effect happens.

Does the above make sense?

Matteo




--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Steve Freeman

unread,
Sep 22, 2016, 3:50:19 AM9/22/16
to growing-object-o...@googlegroups.com
Exactly. That's where the original thinking behind mocks came from. Calling changeEmail() should trigger the object to interact with its environment, so that's what you're looking for. If it's a change of state within the object, then I would expect that to change the object's behaviour in some observable way.

S
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>
>
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Dariusz Gafka

unread,
Sep 22, 2016, 5:13:53 AM9/22/16
to Growing Object-Oriented Software
So I should always search for some kind of side effects afer calling an method. 

"If it's a change of state within the object, then I would expect that to change the object's behaviour in some observable way"
In this example, only what "changeEmail" method changes is the internal state of the object. 

So I should expose some interface, which will inform me about state being changed e.g. "getEmail()"?
Or maybe, when there is just "setter" method, there is no much to test then? 
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>
>
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Steve Freeman

unread,
Sep 22, 2016, 5:30:08 AM9/22/16
to growing-object-o...@googlegroups.com
There's no single answer.

The question to ask is, "if this thing worked, how could I tell?" What is the point of the call that you're making? If it's setting a private field, what is the expected effect of that change?

If the call doesn't trigger a reaction or change a behaviour, why are you doing it?

S

Angel Java Lopez

unread,
Sep 22, 2016, 5:50:27 AM9/22/16
to growing-object-o...@googlegroups.com
Hi!

Limited English here ;-)

Usually, if you change the email, something in the behavior will change.

Suppose User has a method

user.notify("hi!");

If it were my team code, internally, user.notify should call a helper object, of class

MyGmailServer server;

with

server.send(this.email, message);

But that is in the final code. Using TDD, my first implementation will be:

EmailServer server; 

and fill this helper with MyInMemoryEmailServer

EmailServer server = new MyInMemoryEmailServer();
User user = new User(server);

user.changeEmail("mynew...@gmail.com");
user.notify("hi!");

Email email server.getLastEmail();

Assert.assertNotNull(email);
Assert.assertEquals("mynew...@gmail.com", email.getTo());
Assert.assertEquals("hi!", email.getMessage());

and so on

My team could wrote an spike program, testing (outside TDD) the good behavior using the real Gmail server object.

At live demo (at end of iteration), we could use MyInMemoryEmailServer, with a web page listing the emails in that object, or we could use MyGmailServer. And the change could be on the fly, by dynamic configuration (please no @Autowire from Spring Framework ;-)

In my jargon, MyInMemoryEmailServer IS NOT A mock, or stub, but an alternative (and first initial) implementation of a service that evolves with the development of the software. And the same implementation is used along many TDD tests

Instead of email, I could use Telegram, Whatsapp, Slack, email.... as concrete final objects, but with an initial/alternative implementation of Notifier with

.notify(username, message)

or something alike. Many spikes (integration programs) can consume Telegram, Whatsapp, in integration tests, so the team can be sure about the final expected behavior. And change to InMemoryNotifier, Whatsapp, Telegram, or what else, on the fly... in demos and final programs.

So, in the first write of User.notify, we are not stucked with the final helper. Following simplicity, we implement a simpler helper, reused along the software, that can be easy changed to a more powerful/real helper at any moment (integration tests, demos, etc)

Angel "Java" Lopez
@ajlopez


Dariusz Gafka

unread,
Sep 22, 2016, 6:22:30 AM9/22/16
to Growing Object-Oriented Software
Thanks for such big example @Angel. 
In fact I am doing similar things in my applications and switch the implementations depending on the context / need. 
But I wouldn't go so far to inject Notification inside User. 

In fact yes, I am calling changeEmail only to change user's email property.  So later it can be used to send him an email for example.

The thing is that in my context I am having light version of CQRS. Where domain model, which I unit test is only based on writes and read model are just SQLs quries executed on tables used by write model.
I know, that if I had events I could just catch "EmailWasChangedEvent" or something similar, but I have none.  

Now sending an email go through Read Model. So there is no "getEmail()" method in the domain, because I get the data directly from database avoiding using domain models.


So like you can see from domain perspective there is no side effects, which I can catch while changing the email.

W dniu czwartek, 22 września 2016 11:50:27 UTC+2 użytkownik Angel Java Lopez napisał:
Hi!

Limited English here ;-)

Usually, if you change the email, something in the behavior will change.

Suppose User has a method

user.notify("hi!");

If it were my team code, internally, user.notify should call a helper object, of class

MyGmailServer server;

with

server.send(this.email, message);

But that is in the final code. Using TDD, my first implementation will be:

EmailServer server; 

and fill this helper with MyInMemoryEmailServer

EmailServer server = new MyInMemoryEmailServer();
User user = new User(server);

user.changeEmail("mynew...@gmail.com");
user.notify("hi!");

Email email server.getLastEmail();

Assert.assertNotNull(email);
Assert.assertEquals("mynewemail...@gmail.com", email.getTo());

Josue Barbosa dos Santos

unread,
Sep 22, 2016, 8:50:46 AM9/22/16
to growing-object-o...@googlegroups.com

2016-09-22 7:22 GMT-03:00 Dariusz Gafka <darius...@gmail.com>:
So like you can see from domain perspective there is no side effects, which I can catch while changing the email.

Hi!

Limited English here too ;-)

I disagree that there is no side effects because when you call user.changeEmail("value") the state of user will change. Even if you do not have a public method to access the state (user.getMail()). And more, if you are persisting the email value in a database you are accessing the state. Even if the access to the state is made by a framework that does it by reflection (for example).  Your application HAS to access the value to be apple to persist it.

Once I was a lot more worried about encapsulation. With TDD and good programming practices I became less. So, in your case I would create user.getMail() to observe the side effect. But if I am really upset about creating it only for testing purpose I would do something like it:

/* Not sure about names. As always. :) Pseudo Java */

interface EmailChanger {
    changeEmail(Email email)
}
  
interface EmailHolder {
    Email getEmail()
}


class User implements EmailChanger, EmailHolder{
...
}

And use a reference do EmailChanger instead of User in the places where the mail is changed in my domain code. And cast User to EmailHolder in my tests methods.

May me a simpler strategy would be (or the strategy above is not possible):

class User {
    Email email

    changeEmail(Email email){
       this.email = email
    }

}

class UserForTesting extends User{

    Email getEmail(){
        return email
    }

}

Daniel Wellman

unread,
Sep 22, 2016, 9:28:12 AM9/22/16
to growing-object-o...@googlegroups.com
Hi Dariusz,

> On Sep 22, 2016, at 6:22 AM, Dariusz Gafka <darius...@gmail.com> wrote:
>
> ...
>
> The thing is that in my context I am having light version of CQRS. Where domain model, which I unit test is only based on writes and read model are just SQLs quries executed on tables used by write model.
> I know, that if I had events I could just catch "EmailWasChangedEvent" or something similar, but I have none.

So is changeEmail() is part of the write model? If you aren't using events to make changes to your database, then does changeEmail() write to the database? If not, how does that happen?

How does the code in the read model find the user record? Is it some shared database access code between the read and write layers or do both models know that users are stored in the table named "users" (or some other name)?

Dan

George Dinwiddie

unread,
Sep 22, 2016, 9:41:37 AM9/22/16
to growing-object-o...@googlegroups.com
Dariusz,

On 9/22/16 6:22 AM, Dariusz Gafka wrote:
> In fact yes, I am calling changeEmail only to change user's email
> property. So later it can be used to send him an email for example.

What happens when you later send him an email?

- George

--
----------------------------------------------------------------------
* George Dinwiddie * http://blog.gdinwiddie.com
Software Development http://www.idiacomputing.com
Consultant and Coach http://www.agilemaryland.org
----------------------------------------------------------------------

Dariusz Gafka

unread,
Sep 22, 2016, 10:18:10 AM9/22/16
to Growing Object-Oriented Software
@Daniel
The last one. Read Model and Write Model both know about same table "users". 

Dariusz Gafka

unread,
Sep 22, 2016, 10:22:26 AM9/22/16
to Growing Object-Oriented Software
@George,
The email was just an example. 
Let's say nothing happens, he receives an email reads it and that's all :)

Colin Vipurs

unread,
Sep 22, 2016, 10:27:21 AM9/22/16
to growing-object-o...@googlegroups.com
In order to receive an email, something somewhere in your system must interact with the email address.   Whether you store it for another process to read it or send it now.  This is your side effect that you can verify.

I personally would not expose a getEmail() field to verify it, I'd focus purely on the behaviour the system is going to exhibit when the email is changed.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Maybe she awoke to see the roommate's boyfriend swinging from the chandelier wearing a boar's head.

Something which you, I, and everyone else would call "Tuesday", of course.

George Dinwiddie

unread,
Sep 22, 2016, 11:21:50 AM9/22/16
to growing-object-o...@googlegroups.com
Dariusz,

How does he read the email if it's never sent? If nothing every uses the
changed email address, then there's no point in the changeEmail() method
doing anything, and no test is needed. I don't think that's your intent,
however.

What does the system do when you want to send an email to the user?

- George
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "Growing Object-Oriented Software" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to
> growing-object-oriente...@googlegroups.com
> <mailto:growing-object-oriente...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.

Daniel Wellman

unread,
Sep 22, 2016, 7:11:33 PM9/22/16
to growing-object-o...@googlegroups.com
Darius,

Ok, thank you for clarifying. So is this how it works?

- The changeEmail() method in the User class (the "write" portion of the model) executes something like "UPDATE users SET email_address=..." (assuming SQL)
- The code that sends email (in the "read" part of the application) executes something like "SELECT email_address FROM users ..." to fetch the email address which we changed above. This code is NOT in the User class above. 

If this is correct, then the test for User.changeEmailAddress() verifies the database is updated correctly, eg "SELECT email_address FROM users ...". The test that checks sending email addresses must first INSERT a row with the email address into the database to simulate what the "write" side of the application.  This would mean that the two sides of the application know the database structure. If the schema changes, then at least two parts of the code would change. 

If you wanted to put the knowledge of how the database is structured into one place, then I might extract an object (maybe "AddressBook", but you might know a better name) that had a method to update the user email address and one to read the address/user. Then you can test the User object using a fake AddressBook - a mock object that verifies the right message was sent or an InMemoryAddressBook. Then you can test the write part of your application by setting up a fake AddressBook and make sure your email is sent to the right person. 

Given what I know, I'd probably do the latter solution. I'd also consider having a separate interface for the read portion and write sides to keep each side from accidentally reading or writing. The AddressBook could implement both interfaces so the database structure only lives in one place. 

Please let me know if I misunderstood, and please let me know if my suggestion sounds like a mistake. :-)

Cheers
Dan
--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Dariusz Gafka

unread,
Sep 23, 2016, 1:39:48 AM9/23/16
to Growing Object-Oriented Software
George, 
I meant, that email is just an example. Same problem arrives when we have data like "Name", "Surname", which is needed only for reading. For example to show it on user's profile. 

Daniel,
Exactly, it works like that :). 
Write Model -> ORM (saves to "users")
Read Model -> only SQLs (read from "users")

You got me there, if the schema changes it needs to be changed in two places. That's the bottleneck of this solution, but I am aware of that.
For reading data I don't want to rebuild all domain objects and mess with them. It's much simplier and cleaner for Write Model, to operate on SQLs directly.

This is somehow scary, that from unit tests we came to overall application architecture. 
And leads me to thinking, that there is more things, that I should consider that I thought at the beginning.


With current solution I can of course test it using functional tests with In Memory implementation. Where scenario contains changing user's mail and then sending an email. 
Same about name and surname.
But this is on higher level and then unit testing "changeEmail()" has still no sense.


To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Steve Freeman

unread,
Sep 23, 2016, 3:24:49 AM9/23/16
to growing-object-o...@googlegroups.com
If the application is this simple, this might be a perfectly good solution. Perhaps testing "below" the user interface to keep things moving.

S

Colin Vipurs

unread,
Sep 23, 2016, 4:05:27 AM9/23/16
to growing-object-o...@googlegroups.com
Furthermore - if it really is this simple, as in CC=1 I would personally just have an end to end test with a DB running in something like Docker and test it that way.

I'd do as Steve suggest and go under the "UI" for any alternative paths and if the app takes more than a couple of seconds to startup. The same goes for the DB layer if I don't have the ability to have a clean one running on my machine and as part of CI. You've then of course still got the issue of testing the real implementation anyway.

Sent from my iPhone
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Dariusz Gafka

unread,
Sep 23, 2016, 7:51:51 AM9/23/16
to Growing Object-Oriented Software
Okey, thanks for all your answers. 
I will go with functional tests for such situations :)
> To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

George Dinwiddie

unread,
Sep 23, 2016, 11:18:06 AM9/23/16
to growing-object-o...@googlegroups.com
Dariusz,

On 9/23/16 1:39 AM, Dariusz Gafka wrote:
> George,
> I meant, that email is just an example. Same problem arrives when we
> have data like "Name", "Surname", which is needed only for reading. For
> example to show it on user's profile.

Yes, I realize it's an example. That's why I was trying to use it for
discussion. (BTW, don't assume that "Name" and "Surname" will never change.)

>
> Daniel,
> Exactly, it works like that :).
> Write Model -> ORM (saves to "users")
> Read Model -> only SQLs (read from "users")
>
> You got me there, if the schema changes it needs to be changed in two
> places. That's the bottleneck of this solution, but I am aware of that.
> For reading data I don't want to rebuild all domain objects and mess
> with them. It's much simplier and cleaner for Write Model, to operate on
> SQLs directly.

I have always found benefit in putting both read and write in the same
place. This is what Uncle Bob calls the Common Closure Principle
(http://c2.com/cgi/wiki?CommonClosurePrinciple). Otherwise the database
becomes a giant global variable where different clients can interpret
the values and structure differently.

The distance between access of a variable is important. When the access
is within a single method, it's pretty easy to consider them all when
changing one of them. When it's in separate files or modules, it's much
harder. That's why global variables have generally fallen out of favor.

>
> This is somehow scary, that from unit tests we came to overall
> application architecture.

That's a common occurrence, and one of the big benefits of test driven
development. It's not about testing, but driving the design.

> And leads me to thinking, that there is more things, that I should
> consider that I thought at the beginning.

I recommend always reconsidering past decisions and assumptions. You
should always know more now than you did in the past.

>
>
> With current solution I can of course test it using functional tests
> with In Memory implementation. Where scenario contains changing user's
> mail and then sending an email.
> Same about name and surname.
> But this is on higher level and then unit testing "changeEmail()" has
> still no sense.

I'd say that changeEmail() is an incomplete unit. It, coupled with the
code that reads the value, form a unit. A unit test should test that the
value read is the same as the one written. If you've got multiple
clients doing the reading, then you've got multiple units all sharing
the changeEmail() method. Each of them needs testing to make sure they
all change in unison.

- George
>> growing-object-oriente...@googlegroups.com
>> <javascript:>.
>> For more options, visit https://groups.google.com/d/optout
>> <https://groups.google.com/d/optout>.
>
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "Growing Object-Oriented Software" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to
> growing-object-oriente...@googlegroups.com
> <mailto:growing-object-oriente...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages