PDA

View Full Version : Mocking Win32 API


Michael Feathers
07-03-2003, 09:58 AM
"Alvin777" <alvin777@mail.ru> wrote in message
news:1c9319e1.0307030807.64f25c31@posting.google.com... How can I use something like mock objects when calling Win API functions like this? [C++] class WrMAPISession { public: void Logon(); ... } void WrMAPISession::Logon() { ... MAPILogonEx(....); ... } Write a class, which consists of one function - MAPILogonEx - and use its instance as a parameter when calling Logon()? That doesn't sound good.

It's just a function, right? Here are two techniques:

1) If you have your class in files named wrmapisession.h, and
wrmapisession.cpp make another file called testwrmapisession.cpp. Is should
be empty except for this line:

#include "wrmapisession.cpp"

Compile testwrmapisession.cpp and link it to your testharness.

When you want to mock out a function, add it to the file before the include:

/* include whatever makes windows happy here */
HRESULT MAPILogonEx(
ULONG ulUIParam,
LPTSTR lpszProfileName,
LPTSTR lpszPassword,
FLAGS flFlags,
LPMAPISESSION FAR * lppSession
) { return S_OK; }

#include "wrmapisession.cpp"


2) You can try to make fake functions like the one above and put them in a
library and link to them rather than the rest of windows. This one's called
'link time polymorphism' and I learned it from Brian Button.


Michael Feathers
www.objectmentor.com

John Elrick
07-03-2003, 11:44 AM
"Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message
news:be1qtu$gso$1@slb3.atl.mindspring.net... "Alvin777" <alvin777@mail.ru> wrote in message news:1c9319e1.0307030807.64f25c31@posting.google.com...

SNIP
Write a class, which consists of one function - MAPILogonEx - and use its instance as a parameter when calling Logon()? That doesn't sound good. It's just a function, right? Here are two techniques: 1) If you have your class in files named wrmapisession.h, and wrmapisession.cpp make another file called testwrmapisession.cpp. Is
should be empty except for this line:

Hi Michael!

Looks like a nice solution. C++ isn't one of my languages...in Delphi I've
learned that a problem like this is a smell that there is a violation of
DIP.

That's why I'd move to an adapter based on an interface, with an interface
based factory to generate it. Makes it _very_ easy to swap out test vs
production or different production implementations.


John Elrick

Alvin777
07-04-2003, 02:26 AM
"Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message news:<be1qtu$gso$1@slb3.atl.mindspring.net>... "Alvin777" <alvin777@mail.ru> wrote in message news:1c9319e1.0307030807.64f25c31@posting.google.com...

It's just a function, right? Here are two techniques: 1) If you have your class in files named wrmapisession.h, and wrmapisession.cpp make another file called testwrmapisession.cpp. Is should be empty except for this line: #include "wrmapisession.cpp" Compile testwrmapisession.cpp and link it to your testharness. When you want to mock out a function, add it to the file before the include: /* include whatever makes windows happy here */ HRESULT MAPILogonEx( ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR * lppSession ) { return S_OK; } #include "wrmapisession.cpp" 2) You can try to make fake functions like the one above and put them in a library and link to them rather than the rest of windows. This one's called 'link time polymorphism' and I learned it from Brian Button. Michael Feathers www.objectmentor.com

This method doesn't suite me well. Since I link my test exe with .lib,
containing all tested code.

Michael Feathers
07-04-2003, 07:44 AM
"John Elrick" <jelrick@adelphia.net> wrote in message
news:4I%Ma.9802$Hw.6709192@news2.news.adelphia.net... "Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message news:be1qtu$gso$1@slb3.atl.mindspring.net... "Alvin777" <alvin777@mail.ru> wrote in message news:1c9319e1.0307030807.64f25c31@posting.google.com... SNIP Write a class, which consists of one function - MAPILogonEx - and use its instance as a parameter when calling Logon()? That doesn't sound good. It's just a function, right? Here are two techniques: 1) If you have your class in files named wrmapisession.h, and wrmapisession.cpp make another file called testwrmapisession.cpp. Is should be empty except for this line: Hi Michael! Looks like a nice solution. C++ isn't one of my languages...in Delphi
I've learned that a problem like this is a smell that there is a violation of DIP.

Yep, the real issue is: why are we mocking at all? If I'm trying to get a
C++ class into a test harness to add a feature or do some refactoring
unrelated to the offending functions, I'll often do the dummy function trick
just to get past all of that direct API usage, to deal with one problem at a
time.
That's why I'd move to an adapter based on an interface, with an interface based factory to generate it. Makes it _very_ easy to swap out test vs production or different production implementations.

Adapters are great, but I don't like to "settle" the interface too soon.
Even though a signature like: "HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR
lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR *
lppSession)" is a crime against nature, often I'll mock it directly by
adding a method with exactly that signature to a class. Then when I can go
back and forth with the mock and production classes I'll start to make
higher level methods hat eventually hide the API, often I get them by paying
attention to duplication and feature envy. The nice thing is, with the mock
function in place, all the code that I move onto the new class can be
tested. And, that is pretty good because it can safely grow into a healthy
abstraction, something more than just an adapter.

Michael Feathers
www.objectmentor.com

Michael Feathers
07-07-2003, 06:36 AM
"John Elrick" <jelrick@adelphia.net> wrote in message
news:DZdOa.10466$Hw.7651148@news2.news.adelphia.net... "Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message news:be47e8$uq2$1@slb3.atl.mindspring.net... SNIP I've learned that a problem like this is a smell that there is a violation
of DIP. Yep, the real issue is: why are we mocking at all? If I'm trying to get
a C++ class into a test harness to add a feature or do some refactoring unrelated to the offending functions, I'll often do the dummy function trick just to get past all of that direct API usage, to deal with one problem
at a time. That's why I'd move to an adapter based on an interface, with an interface based factory to generate it. Makes it _very_ easy to swap out test
vs production or different production implementations. Adapters are great, but I don't like to "settle" the interface too soon. Even though a signature like: "HRESULT MAPILogonEx(ULONG ulUIParam,
LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR * lppSession)" is a crime against nature, often I'll mock it directly by adding a method with exactly that signature to a class. Then when I can go back and forth with the mock and production classes I'll start to make higher level methods hat eventually hide the API, often I get them by paying attention to duplication and feature envy. The nice thing is, with the mock function in place, all the code that I move onto the new class can be tested. And, that is pretty good because it can safely grow into a healthy abstraction, something more than just an adapter. Hmmm...so you're saying that by mocking the function, you are delaying the need to update two interfaces (the production and the test) until the interface signature defines itself via its needs...if I'm reading right. That makes more sense to me now.

Yes. One of the sad things when you work with legacy code is that you want
to do something and you want to do it 'right' but you if you are doing it
'right' you can end up with a lot of work between you and the narrow thing
that you need to get done (add a feature for instance). I like to get to
the narrow thing with safe but not pretty steps and then go back and clean
things up as needed.

Michael Feathers
www.objectmentor.com

Dale King
07-08-2003, 03:32 PM
"Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message
news:bec0mn$h1p$1@slb6.atl.mindspring.net... "John Elrick" <jelrick@adelphia.net> wrote in message news:DZdOa.10466$Hw.7651148@news2.news.adelphia.net... "Michael Feathers" <mfeathers@objectmentorNOSPAM.com> wrote in message news:be47e8$uq2$1@slb3.atl.mindspring.net... SNIP I've > learned that a problem like this is a smell that there is a
violation of > DIP. Yep, the real issue is: why are we mocking at all? If I'm trying to
get a C++ class into a test harness to add a feature or do some refactoring unrelated to the offending functions, I'll often do the dummy function trick just to get past all of that direct API usage, to deal with one
problem at a time. > That's why I'd move to an adapter based on an interface, with an interface > based factory to generate it. Makes it _very_ easy to swap out test vs > production or different production implementations. Adapters are great, but I don't like to "settle" the interface too
soon. Even though a signature like: "HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR
* lppSession)" is a crime against nature, often I'll mock it directly by adding a method with exactly that signature to a class. Then when I
can go back and forth with the mock and production classes I'll start to make higher level methods hat eventually hide the API, often I get them by paying attention to duplication and feature envy. The nice thing is, with
the mock function in place, all the code that I move onto the new class can be tested. And, that is pretty good because it can safely grow into a healthy abstraction, something more than just an adapter. Hmmm...so you're saying that by mocking the function, you are delaying
the need to update two interfaces (the production and the test) until the interface signature defines itself via its needs...if I'm reading right. That makes more sense to me now. Yes. One of the sad things when you work with legacy code is that you
want to do something and you want to do it 'right' but you if you are doing it 'right' you can end up with a lot of work between you and the narrow thing that you need to get done (add a feature for instance). I like to get to the narrow thing with safe but not pretty steps and then go back and clean things up as needed.


I would add that you still haven't eliminated the need to mock the original
function by using an adapter. The test code for the adapter still needs to
mock it to be tested, so the problem remains although you do at least
isolate it to only one spot.
--
Dale King


MyLounge.com Site Map
Forum: Cars, Cell Phone, Database, Games, Home Improvement, IT, Music, School, Sports, Web Design, Web Server, Weight Loss

The MyLounge.com forum is intended for informational use only and should not be relied upon and is not a substitute for any advice. The information contained on MyLounge.com are opinions and suggestions of members and is not a representation of the opinions of MyLounge.com. MyLounge.com does not warrant or vouch for the accuracy, completeness or usefulness of any postings or the qualifications of any person responding. Please consult a expert or seek the services of an attorney in your area for more accuracy on your specific situation. Please note that our forums also serve as mirrors to Usenet newsgroups. Many posts you see on our forums are made by newsgroup users who may not be members of MyLounge.com Term of Service