Unit Testing – How It Works

unit testing

I am trying to make my code more robust and I have been reading about unit testing, but I find it very hard to find an actual useful use.
For instance, the Wikipedia example:

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be

assert(adder.add(a, b) == (a+b));

but then this is just coding the function itself in the test.
Can someone provide me with an example where unit testing is actually useful? FYI I am currently coding mostly "procedural" functions that take ~10 booleans and a few ints and give me an int result based on this, I feel like the only unit testing I could do would be to simply re-code the algorithm in the test. edit:
I should also have precised this is while porting (possibly badly designed) ruby code (that I didn't make)

Best Answer

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 :)

Related Topic