Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I unit test a method that sets internal data, but doesn't return?

From what I’ve read, unit test should test only one function/method at a time. But I’m not clear on how to test methods that only set internal object data with no return value to test off of, like the setvalue() method in the following Python class (and this is a simple representation of something more complicated):

class Alpha(object):

    def __init__(self):
        self.__internal_dict = {}

    def setvalue(self, key, val):
        self.__internal_dict[key] = val

    def getvalue(self, key):
        return self.__internal_dict[key]

If unit test law dictates that we should test every function, one at a time, then how do I test the setvalue() method on its own? One "solution" would be to compare what I passed into setvalue() with the return of getvalue(), but if my assert fails, I don't know which method is failing - is it setvalue() or getvalue()? Another idea would be to compare what I passed into setvalue() with the object's private data, __internal_dict[key] - a HUGE disgusting hack!

As of now, this is my solution for this type of problem, but if the assert raises, that would only indicate that 1 of my 2 main methods is not properly working.

import pytest

def test_alpha01():
    alpha = Alpha()
    alpha.setvalue('abc', 33)

    expected_val = 33
    result_val = alpha.getvalue('abc')

    assert result_val == expected_val

Help appreciated

like image 933
Paul W Avatar asked Jul 22 '16 15:07

Paul W


1 Answers

The misconception

The real problem you have here is that you are working on a false premise:

If unit test law dictates that we should test every function, one at a time...

This is not at all what good unit testing is about.

Good unit testing is about decomposing your code into logical components, putting them into controlled environments and testing that their actual behaviour matches their expected behaviour - from the perspective of a consumer.

Those "units" may be (depending on your environment) anonymous functions, individual classes or clusters of tightly-coupled classes (and don't let anyone tell you that class coupling is inherently bad; some classes are made to go together).

The important thing to ask yourself is - what does a consumer care about?

What they certainly don't care about is that - when they call a set method - some internal private member that they can't even access is set.

The solution

Naively, from looking at your code, it seems that what the consumer cares about is that when they call setvalue for a particular key, calling getvalue for that same key gives them back the value that they put in. If that's the intended behaviour of the unit (class), then that's what you should be testing.

Nobody should care what happens behind the scenes as long as the behaviour is correct.

However, I would also consider if that's really all that this class is for - what else does that value have an impact on? It's impossible to say from the example in your question but, whatever it is, that should be tested too.

Or maybe, if that's hard to define, this class in itself isn't very meaningful and your "unit" should actually be an independent set of small classes that only really have meaningful behaviour when they're put together and should be tested as such.

The balance here is subtle, though, and difficult to be less cryptic about without more context.

The pitfall

What you certainly shouldn't (ever ever ever) do is have your tests poking around internal state of your objects. There are two very important reasons for this:

First, as already mentioned, unit tests are about behaviour of units as perceived by a client. Testing that it does what I believe it should do as a consumer. I don't - and shouldn't - care about how it does it under the hood. That dictionary is irrelevant to me.

Second, good unit tests allow you to verify behaviour while still giving you the freedom to change how that behaviour is achieved - if you tie your tests to that dictionary, it ceases to be an implementation detail and becomes part of the contract, meaning any changes to how this unit is implemented force you either to retain that dictionary or change your tests.

This is a road that leads to the opposite of what unit testing is intended to achieve - painless maintenance.

The bottom line is that consumers - and therefore your tests - do not care about whether setvalue updates an internal dictionary. Figure out what they actually care about and test that instead.

As an aside, this is where TDD (specifically test-first) really comes into its own - if you state the intended behaviour with a test up-front, it's difficult to find yourself stuck in that "what am I trying to test?" rut.

like image 184
Ant P Avatar answered Sep 28 '22 11:09

Ant P