Mocking local objects

Mocking local objects

Post by Alvin7 » Wed, 02 Jul 2003 18:36:06



I have following C++ code:

SomeClass::ShowDialog()
{
  SomeDlgClass oDlg;
  oDlg.DoModal();
  m_Value = oDlg.GetSomeUserEnteredValue();

Quote:}

I'd like to test SomeClass and use mock object instead of
SomeDlgClass. That mock object do not display dlg at DoModal() and
returns expected value at GetSomeUserEnteredValue().

But how can I replace SomeDlgClass with MockedSomeDlgClass without
changing the code?

It is easy if I'd used pointers:

SomeClass::ShowDialog(SomeDlgClass* pDlg)
{
  pDlg->DoModal();
  m_Value = pDlg->GetSomeUserEnteredValue();

Quote:}

Then I just should call ShowDialog() with mock object. But what to do
if object created locally?

--
Alvin777

 
 
 

Mocking local objects

Post by 4Spac » Wed, 02 Jul 2003 18:59:45


Hi Alvin,

You've got the classic problem of hard wired dependencies getting in the way
of your testing. Of course, you're on the right track, Mock objects are the
way to go. But you must also break internal dependencies as well as external
ones. Rather than try and relay the techniques for this to you, perhaps I
can redirect you to a paper on this area:

http://www.informatik.fernuni-hagen.de/import/pi3/stefan/Publications...

I'm sure I've read articles on it at objectmentor also.

Seperation of concerns is your friend :)

Cheers,

4Space


Quote:> I have following C++ code:

> SomeClass::ShowDialog()
> {
>   SomeDlgClass oDlg;
>   oDlg.DoModal();
>   m_Value = oDlg.GetSomeUserEnteredValue();
> }

> I'd like to test SomeClass and use mock object instead of
> SomeDlgClass. That mock object do not display dlg at DoModal() and
> returns expected value at GetSomeUserEnteredValue().

> But how can I replace SomeDlgClass with MockedSomeDlgClass without
> changing the code?

> It is easy if I'd used pointers:

> SomeClass::ShowDialog(SomeDlgClass* pDlg)
> {
>   pDlg->DoModal();
>   m_Value = pDlg->GetSomeUserEnteredValue();
> }

> Then I just should call ShowDialog() with mock object. But what to do
> if object created locally?

> --
> Alvin777


 
 
 

Mocking local objects

Post by Michael Feather » Wed, 02 Jul 2003 20:07:50



Quote:> I have following C++ code:

> SomeClass::ShowDialog()
> {
>   SomeDlgClass oDlg;
>   oDlg.DoModal();
>   m_Value = oDlg.GetSomeUserEnteredValue();
> }

> I'd like to test SomeClass and use mock object instead of
> SomeDlgClass. That mock object do not display dlg at DoModal() and
> returns expected value at GetSomeUserEnteredValue().

> But how can I replace SomeDlgClass with MockedSomeDlgClass without
> changing the code?

> It is easy if I'd used pointers:

> SomeClass::ShowDialog(SomeDlgClass* pDlg)
> {
>   pDlg->DoModal();
>   m_Value = pDlg->GetSomeUserEnteredValue();
> }

Here is a refactoring I called 'parameterize method':

You have this code:

void SomeClass::ShowDialog()
{
      SomeDlgClass oDlg;
      oDlg.DoModal();
      m_Value = oDlg.GetSomeUserEnteredValue();

Quote:}

Create a new empty method with the same name:

void SomeClass::ShowDialog()
{

Quote:}

Copy in the the code from the original method except for the declaration of
the local:

void SomeClass::ShowDialog()
{
      oDlg.DoModal();
      m_Value = oDlg.GetSomeUserEnteredValue();

Quote:}

Your compiler yells at you because it needs to know what oDlg is, so add
oDlg but as a parameter:

void SomeClass::ShowDialog(SomeDlgClass& oDlg)
{
      oDlg.DoModal();
      m_Value = oDlg.GetSomeUserEnteredValue();

Quote:}

Replace the code in the original ShowDialog with a call to the new one:

void SomeClass::ShowDialog()
{
      SomeDlgClass oDlg;
      ShowDialog(oDlg);

Quote:}

Your code should now look like this: two ShowDialog methods.  If you want to
test, you can call the latter one with a mock.

void SomeClass::ShowDialog()
{
      SomeDlgClass oDlg;
      ShowDialog(oDlg);

Quote:}

void SomeClass::ShowDialog(SomeDlgClass& oDlg)
{
      oDlg.DoModal();
      m_Value = oDlg.GetSomeUserEnteredValue();

Quote:}

Michael Feathers
www.objectmentor.com
 
 
 

Mocking local objects

Post by Alvin7 » Thu, 03 Jul 2003 23:50:40


Thanx! Looks like the thing I need.

--
Alvin777

 
 
 

Mocking local objects

Post by Michael Feather » Fri, 04 Jul 2003 00:44:05



Quote:> Thanx! Looks like the thing I need.

You're welcome.  One thing I forgot to mention.  Often when I do
'parameterize method', I put a "with" in the name I am extracting.  In your
example you'd end up with:

void SomeClass::ShowDialog()

and:

void SomeClass:ShowDialogWithDialog(SomeDlgClass& oDlg)

There are times when it looks less goofy than this.

Michael Feathers
www.objectmentor.com

 
 
 

Mocking local objects

Post by Greg Bac » Fri, 04 Jul 2003 01:35:16




: [...]
:
: Your code should now look like this: two ShowDialog methods.  If you
: want to test, you can call the latter one with a mock.
:
: void SomeClass::ShowDialog()
: {
:       SomeDlgClass oDlg;
:       ShowDialog(oDlg);
: }
:
: void SomeClass::ShowDialog(SomeDlgClass& oDlg)
: {
:       oDlg.DoModal();
:       m_Value = oDlg.GetSomeUserEnteredValue();
: }

How do you write a test to mitigate the risk of skew between the canned
and parameterized versions of ShowDialog?  Would it be better to divert
everyone to the parameterized method?

Greg
--
They that can give up essential liberty to obtain a little temporary
safety deserve neither liberty nor safety.
    -- Ben Franklin

 
 
 

Mocking local objects

Post by Michael Feather » Fri, 04 Jul 2003 02:34:47





> : [...]
> :
> : Your code should now look like this: two ShowDialog methods.  If you
> : want to test, you can call the latter one with a mock.
> :
> : void SomeClass::ShowDialog()
> : {
> :       SomeDlgClass oDlg;
> :       ShowDialog(oDlg);
> : }
> :
> : void SomeClass::ShowDialog(SomeDlgClass& oDlg)
> : {
> :       oDlg.DoModal();
> :       m_Value = oDlg.GetSomeUserEnteredValue();
> : }

> How do you write a test to mitigate the risk of skew between the canned
> and parameterized versions of ShowDialog?

In general, I just don't do it.  Refactorings like this make testing
possible where it wouldn't be otherwise.  When I do these I think of what
could go wrong and do the refactoring carefully with a partner.  In some of
these 'refactorings-to-get-things-under-test,' the worst thing that can
happen is something I call a 'connection error': you forget to make a call
in the code when you should have made a call.  In this case, if we botched
the parameterize method refactoring, the dialog box would just not show up
in the application.  It is something you can make note of and check for.

It's good to be conscious of the slippery slope.  You shouldn't have to do
too many of these unprotected refactorings to get things under test.

Quote:> Would it be better to divert everyone to the parameterized method?

I don't.  The original method was convienient for the clients, so I let then
use that.  Moreover, if they do use it, we can see whether we made a
connection error quickly.

Sometimes the parameterized method is found useful by a client.  That's
great, frosting on the cake.

Michael Feathers
www.objectmentor.com