Created
February 2, 2011 13:24
-
-
Save alamaison/807670 to your computer and use it in GitHub Desktop.
Preferred C++ version using templates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <algorithm> | |
#include <iostream> | |
#include <set> | |
#include <string> | |
#include <vector> | |
using std::cerr; | |
using std::cout; | |
using std::set; | |
using std::string; | |
using std::vector; | |
class Data {}; | |
class User | |
{ | |
public: | |
User(const string& /*name*/, int /*shoe_size*/) {} | |
void setGravatar(const string& /*grav*/) {} | |
void setProfilePage(const string& /*profile*/) {} | |
Data locallyCachedNewData() { return Data(); } | |
}; | |
/** | |
* Our database knows all the things we need, but makes costly calls to find it | |
*/ | |
class Database | |
{ | |
public: | |
set<string> getAllUserNames() | |
{ | |
cout << "doSomeLengthyDatabaseLookupProcedureThatMakesThisCallTakeMore" | |
"ThanThreeAndAHalfMillseconds()" << std::endl; | |
set<string> names; | |
names.insert("Harry"); | |
names.insert("Sally"); | |
return names; | |
} | |
User getUser(string /*username*/) | |
{ | |
cout << "lookupAllTheUsersDataInTheLocalDatabase()" << std::endl; | |
return User("Harry", 12); | |
} | |
}; | |
/** | |
* Our adapter talks to some website. It's an expensive set of ops to run. | |
*/ | |
class ApiAdapter | |
{ | |
public: | |
string getGravatar(const string& /*name*/) | |
{ | |
cout << "communicateToNetwork()" << std::endl; | |
return "http://gravatar.example.com"; | |
} | |
string getProfilePage(const string& /*name*/) | |
{ | |
cout << "communicateToNetwork()" << std::endl; | |
return "http://profile.example.com"; | |
} | |
void updateWithLocallyCachedNewData(string /*name*/, Data /*newData*/) | |
{ | |
cout << "communicateToNetwork()" << std::endl; | |
} | |
}; | |
template<typename A, typename D> | |
class MyModelClass | |
{ | |
private: | |
A mAdapter; | |
D mDatabase; | |
public: | |
MyModelClass(A adapter, D someDatabaseINeedToAccess) : | |
mAdapter(adapter), mDatabase(someDatabaseINeedToAccess) {} | |
/* | |
* This is the method we want to test | |
*/ | |
void populateTheDatabaseWithSomeInfo() | |
{ | |
set<string> names = mDatabase.getAllUserNames(); | |
set<string>::iterator name; | |
for (name = names.begin(); name != names.end(); ++name) | |
{ | |
User localUserData = mDatabase.getUser(*name); | |
localUserData.setGravatar(mAdapter.getGravatar(*name)); | |
localUserData.setProfilePage(mAdapter.getProfilePage(*name)); | |
mAdapter.updateWithLocallyCachedNewData( | |
*name, localUserData.locallyCachedNewData()); | |
} | |
} | |
}; | |
class ActionRecorder | |
{ | |
vector<string> m_actions; | |
public: | |
void recordAction(const string& action) | |
{ | |
m_actions.push_back(action); | |
} | |
bool contains(const string& action) | |
{ | |
return std::find( | |
m_actions.begin(), m_actions.end(), action) != m_actions.end(); | |
} | |
}; | |
class MockDatabase | |
{ | |
ActionRecorder& mActionRecorder; | |
public: | |
MockDatabase(ActionRecorder& recorder) : mActionRecorder(recorder) {} | |
set<string> getAllUserNames() | |
{ | |
mActionRecorder.recordAction("get names"); | |
set<string> userNameSet; | |
userNameSet.insert("James"); | |
return userNameSet; | |
} | |
User getUser(const string& name) | |
{ | |
mActionRecorder.recordAction("checked for user " + name); | |
// Our test only has James | |
return User("James", 5); | |
} | |
}; | |
class MockApiAdapter | |
{ | |
ActionRecorder& mActionRecorder; | |
public: | |
MockApiAdapter(ActionRecorder& recorder) : mActionRecorder(recorder) {} | |
string getGravatar(string name) | |
{ | |
mActionRecorder.recordAction("lookup gravatar " + name); | |
return "http://gravatar.example.com"; | |
} | |
string getProfilePage(string name) | |
{ | |
mActionRecorder.recordAction("lookup profile page " + name); | |
return "http://profile.example.com"; | |
} | |
void updateWithLocallyCachedNewData(string /*name*/, Data /*newData*/) {} | |
}; | |
void my_assert(bool condition, const string& description) | |
{ | |
if (!condition) | |
cerr << "ASSERT FAILED: " << description << std::endl; | |
} | |
#define MY_ASSERT(x) my_assert((x), #x); | |
int main() | |
{ | |
ActionRecorder ar; // We'll write to this | |
// Instantiate our testing object using our mocks | |
MockApiAdapter adapter(ar); | |
MockDatabase db(ar); | |
MyModelClass<MockApiAdapter, MockDatabase> objForQuickTesting(adapter, db); | |
objForQuickTesting.populateTheDatabaseWithSomeInfo(); | |
// It really does pull the names from the database | |
MY_ASSERT(ar.contains("get names")); | |
// It really does actually check the names we supplied it from the DB | |
MY_ASSERT(ar.contains("checked for user James")); | |
MY_ASSERT(ar.contains("lookup gravatar James")); | |
MY_ASSERT(ar.contains("lookup profile page James")); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment