Example: a seam in a C++ function

bool CAsyncSslRec::Init() {
    if (m_bSslInitialized) {
        return true;
    }
    m_smutex.Unlock();
    m_nSslRefCount++;
    m_bSslInitialized = true;
    FreeLibrary(m_hSslDll1);
    m_hSslDll1=0;
    FreeLibrary(m_hSslDll2);
    m_hSslDll2=0;
    if (!m_bFailureSent) {
        m_bFailureSent=TRUE;
        PostReceiveError(SOCKETCALLBACK, SSL_FAILURE);
    }
    CreateLibrary(m_hSslDll1,"syncesel1.dll");
    CreateLibrary(m_hSslDll2,"syncesel2.dll");
    m_hSslDll1->Init();
    m_hSslDll2->Init();
    return true;
}

Suppose we want to run all of Init() except this line:

PostReceiveError(SOCKETCALLBACK, SSL_FAILURE);

We can’t just delete it — it has to stay in production. PostReceiveError is a global function that talks to another subsystem that’s painful under test. So the real problem is: how do we run the method without calling PostReceiveError in a test, while still calling it in production? That question is what leads to the idea of a seam.

A seam is a place where you can alter behavior in your program without editing in that place.

Is there a seam at the PostReceiveError call? Yes. Because PostReceiveError is a global function (not part of CAsyncSslRec), we can add a method with the same signature to the class that just delegates to it via C++‘s scoping operator:

class CAsyncSslRec {
    ...
    virtual void PostReceiveError(UINT type, UINT errorcode);
    ...
};
void CAsyncSslRec::PostReceiveError(UINT type, UINT errorcode) {
    ::PostReceiveError(type, errorcode);
}

Behaviour is preserved — there’s a little indirection, but production still calls the same global function. Now subclass and override the new method to do nothing:

class TestingAsyncSslRec : public CAsyncSslRec {
    virtual void PostReceiveError(UINT type, UINT errorcode) {}
};

If our tests instantiate TestingAsyncSslRec instead of CAsyncSslRec, the call to PostReceiveError inside Init() becomes a no-op, and we can test the method without the nasty side effect.

This is an object seam: we changed which method runs without editing the method that calls it. Object seams are available in object-oriented languages, and they’re just one of several kinds of seams the book covers.

Source

Working Effectively with Legacy Code, Michael Feathers — Chapter 4, The Seam Model.