8

I'm going through this series. The author mentions that since state is maintained in object oriented programming, it is harder to write unit tests. He also says that since functional programming doesn't maintian state (not always) it is easier to write unit tests. I didn't see any examples that demonstrate this problem. If that is true, could you provide me an example comparing unit testing in object oriented and functional programming?

  • 2
    I disagree with idea that functional programming is easier to test. While it is true stateless function is easier to test than stateful object, converting stateful object into stateless function results in much more complex stateles function, possibly with some complex state-emulation, like monads. – Euphoric Jun 29 '17 at 4:44
  • 1
    @Euphoric A functional rendition of a solution to a problem is likely to be quite different than just replacing state with a State monad (which emulates state). You partially have a point - code written in that style does have non-local interactions - but for the purposes of unit testing you still can just explicitly pass in the state, you don't have to mock anything. On the other hand, with monads like ST and IO (which have true, implicit mutable state), you simply are doing stateful programming, and unit testing it will be no easier than in any other language. – Derek Elkins Jun 29 '17 at 5:19
  • 9
    There is nothing in the definition of OO that requires mutable state. It is perfectly possible (and widely done) to write pure, referentially-transparent OO code. – Jörg W Mittag Jun 29 '17 at 8:28
  • 1
    All of the techniques associated with functional programming are perfectly applicable in object-oriented programming. Well designed OO programs typically use functional techniques at the lower levels, at a minimum. – Frank Hileman Jun 29 '17 at 17:56
  • 1
    @Fabio If that is the case it makes more sense to use FP programming. OO does not require mutable state, but mutability is implicit in the example I just gave you. Same goes for f# - you can mark a variable mutable to fit oo needs. But if you are going to use too many mutable variable, you might as well use c#. – user186961 Jul 3 '17 at 3:26
13
+50

I want to distinguish between two different ways of approaching object oriented programming

  1. Simulationist: your objects represent real domain objects, you've programmed them to handle any functionality related to that domain. Objects programmed in this way are likely to have a lot of mutable state and hidden collaborators used to implement this functionality.
  2. Records+functions: your objects are just bundles of data and functions that operates over that data. Objects programmed in this way are more apt to be immutable, to take on less responsibilities and allow one to inject collaborators.

A rule of thumb is that an object programmed in the first way is going to have more methods and more void methods than in the second way. Say we were going to write a flight simulator and were designing a plane class. We would have something like

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

This is maybe a little more extreme than one might encounter, but it gets the point across. If you want to implement this sort of interface you have to keep inside the object

  1. All the information about the state of the plane's equipment.
  2. All the information about the velocity / acceleration of the plane.
  3. The refresh rate of our simulation (to implement tick).
  4. Full details about the 3d model of the simulation and its physics to implement tick.

Writing a unit test for an object written in the mode is dead hard because

  1. You need to supply all of the many different bits of data and collaborators this object needs at the start of your test (instatiating these can be really tedious).
  2. When you want to test a method you run into two problems: a) the interface frequently does not expose enough data for you to test (so you end up having to use mocks / reflection to even verify expectations) b) there are many components bound into one that you have to verify behaved in each test.

Basically you start with an interface that looks kind of reasonable and like it fits the domain well, but the niceness of the simulation has beguiled you into creating an object that's really hard to test.

However, you can create objects that would fulfill the same purpose. You would want to brake your Plane out into smaller bits. Have a PlaneParticle that tracks the physicsy bits of the plane, the position, velocity, acceleration, roll, yaw, etc., etc., exposing and allowing one to manipulate these. Then a PlaneParts object could track the status of. You would ship tick() to a completely different place, say have a PlanePhysics object parameterized by, e.g., the force of gravity, that knows given a PlaneParticle and a PlaneParts how to spit out a new PlaneParticle. All of this could be fully immutable, though it need not be for some example.

You now have these advantages in terms of testing:

  1. Each individual component has less to do and is easier to set up.
  2. You can test your components in isolation.
  3. These objects can get away with exposing their internals (especially if they're made immutable), so one needs no cleverness to measure them.

Here's the trick: the second object-oriented approach I described is very close to functional programming. Maybe in pure functional programs your records and your functions are separate and not bound together in objects, definitely a functional program would ensure that all of these things. What I think really makes unit testing easy is

  1. Small units (small state space).
  2. Functions with minimal input (no hidden inputs).
  3. Functions with minimal output.

Functional programming encourages these things (but one can right bad programs in any paradigm) but they are achievable in object-oriented programs. And I would further stress that functional programming and object-oriented programming are not incompatible.

  • 1
    I am not sure this is a good distinction. Even when using approach 1, many would consider the techniques you describe for approach 2 to be standard practice. The two approaches are not mutually exclusive. The bits of the Plane can be composed together to create a larger abstraction, and you are now in the area of approach 1. I would go as far as saying approach 1, as you describe it, is not logical. – Frank Hileman Jun 29 '17 at 17:52
  • 2
    "And I would further stress that functional programming and object-oriented programming are not incompatible.": Some would say that object-oriented programming is characterized only by dynamic dispatch. So you can write in imperative OOP style: mutable objects + dynamic dispatch, or in functional OOP style: immutable objects + dynamic dispatch. – Giorgio Jul 3 '17 at 4:52
  • 2
    Breaking 1 big object into smaller parts is not functional programming. And the solution to a big object is not to separate the logic from the data and make it anemic, which is functional. I might be misunderstanding your whole however part though. – Chris Wohlert Jul 3 '17 at 8:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy