Case: a "module" (in a broad sence, i.e. something having a public interface and possibly also some private inner parts) has some complicated / involved logic inside it. Testing just the module interface will be sort of an integration testing with relation to the module's inner structure, and thus in case a error is found such testing will not localize the exact inner part / component that is responsible for the failure.
Solution: turn the complicated inner parts into modules themselves, unit-test them (and repeat these steps for them if they are too complicated themselves) and import into your original module. Now you have just a set of modules simple enough to uniit-test (both check that behavior is correct and fix errors) easily, and that's all.
Note:
there will be no need to change anything in tests of the module's (former) "sub-modules" when changing the module's contract, unless the "sub-module"'s no more offer services sufficient to fulfil the new/changed contract.
nothing will be needlessly made public i.e. the module's contract will be kept and the encapsulation maintained.
[Update]
To test some smart internal logic in cases when it is difficult to put object's inner parts (I mean members not the privately imported modules / packages) into appropriate state with just feeding it inputs via the object's public interface:
Unit tests, if you're testing small enough units, are always asserting the blindingly obvious.
The reason that add(x, y)
even gets mention of a unit test, is because sometime later somebody will go into add
and put special tax logic handling code not realizing that add is used everywhere.
Unit tests are very much about the associative principle: if A does B, and B does C, then A does C. "A does C" is a higher-level test. For instance, consider the following, completely legitimate business code:
public void LoginUser (string username, string password) {
var user = db.FetchUser (username);
if (user.Password != password)
throw new Exception ("invalid password");
var roles = db.FetchRoles (user);
if (! roles.Contains ("member"))
throw new Exception ("not a member");
Session["user"] = user;
}
At first glance this looks like an awesome method to unit test, because it has a very clear purpose. However, it does about 5 different things. Each thing has a valid and invalid case, and will make a huge permutation of unit tests. Ideally this is broken down further:
public void LoginUser (string username, string password) {
var user = _userRepo.FetchValidUser (username, password);
_rolesRepo.CheckUserForRole (user, "member");
_localStorage.StoreValue ("user", user);
}
Now we're down to units. One unit test does not care what _userRepo
considers valid behavior for FetchValidUser
, only that it's called. You can use another test to ensure exactly what a valid user constitutes. Similarly for CheckUserForRole
... you've decoupled your test from knowing what the Role structure looks like. You've also decoupled your entire program from being tied strictly to Session
. I imagine all the missing pieces here would look like:
class UserRepository : IUserRepository
{
public User FetchValidUser (string username, string password)
{
var user = db.FetchUser (username);
if (user.Password != password)
throw new Exception ("invalid password");
return user;
}
}
class RoleRepository : IRoleRepository
{
public void CheckUserForRole (User user, string role)
{
var roles = db.FetchRoles (user);
if (! roles.Contains (role))
throw new Exception ("not a member");
}
}
class SessionStorage : ILocalStorage
{
public void StoreValue (string key, object value)
{
Session[key] = value;
}
}
By refactoring you have accomplished several things at once. The program is way more supportive of tearing out underlying structures (you can ditch the database layer for NoSQL), or seamlessly adding locking once you realize Session
isn't thread-safe or whatever. You've also now given yourself very straightfoward tests to write for these three dependencies.
Hope this helps :)
Best Answer
new CloudService()
And there's your problem.
Modern OO design recommends that this sort of dependency be passed in rather than constructed directly. This can be passed into the function itself, or to the class at construction time. It could also be grabbed or aggregated by an Inversion of Control container if that sort of complexity is warranted.
At that point, it becomes fairly trivial to pass in a mock/fake service to provide you with your "online" data during testing. Better yet, it allows your software to be sufficiently flexible, so that you can quickly adapt should some (governmental?) client comes along and doesn't want to use the cloud for their values. Or you want to dump one cloud provider for another. Or...