5

Let's say I have a calculator class who primary function is to do the following (this code is simplified to make the discussion easier, please don't comment on the style of it)

double pilingCarpetArea = (hardstandingsRequireRemediation = true) ? hardStandingPerTurbineDimensionA * hardStandingPerTurbineDimensionB * numberOfHardstandings * proportionOfHardstandingsRequiringGroundRemediationWorks : 0;

double trackCostMultipler;
if (trackConstructionType = TrackConstructionType.Easy) trackCostMultipler = 0.8
else if (trackConstructionType = TrackConstructionType.Normal) trackCostMultipler = 1
else if (trackConstructionType = TrackConstructionType.Hard) trackCostMultipler = 1.3
else throw new OutOfRangeException("Unknown TrackConstructionType: " + trackConstructionType.ToString());

double PilingCostPerArea = TrackCostPerMeter / referenceTrackWidth * trackCostMultipler;

There are at least 7 routes through this class I should probably test, the combination of trackCostMultiplier and hardstandingsRequireRemediation (6 combinations) and the exception condition. I might also want to add some for divide by zero and overflow and suchlike if I was feeling keen.

So far so good, I can test this number of combinations easily and stylishly. And actually I might trust that multiplication and addition are unlikely to go wrong, and so just have 3 tests for trackCostMultipler and 2 for hardstandingsRequireRemediation, instead of testing all possible combinations.

However, this is a simple case, and the logic in our apps is unfortunately cyclomatically much more complicated than this, so the number of tests could grow huge.

There are some ways to tackle this complexity

  1. Extract the trackCostMultipler calculation to a method in the same class

This is a good thing to do, but it doesn't help me test it unless I make this method public, which is a form of "Test Logic In Production". I often do this in the name of pragmatism, but I would like to avoid if I can.

  1. Defer the trackCostMultipler calculation to a different class

This seems like a good thing to do if the calculation is sufficiently complex, and I can test this new class easily. However I have just made the testing of the original class more complicated, as I will now want to pass in a ITrackCostMultipler "Test Double" of some sort, check that it gets called with the right parameters, and check that its return value is used correctly. When a class has, say, ten sub calculators, its unit / integration test becomes very large and difficult to understand.

I use both (1) and (2), and they give me confidence and they make debugging a lot quicker. However there are definitely downsides, such as Test Logic in Production and Obscure Tests.

I am wondering what others experiences of testing cyclomatically complicated code are? Is there a way of doing this without the downsides? I realise that Test Specific Subclasses can work around (1), but this seems like a legacy technique to me. It is also possible to manipulate the inputs so that various parts of the calculation return 0 (for addition or subtraction) or 1 (for multiplication or division) to make testing easier, but this only gets me so far.

Thanks

Cedd

cedd
  • 1,561
  • 1
  • 16
  • 33
  • Use a Functional Programming language and compose the big calculation from smaller functions? – Mark Seemann Aug 06 '15 at 13:59
  • This sounds quite intriguing, could you point me at an example? – cedd Aug 06 '15 at 14:03
  • @cedd you need to @-reply to Mark for him to see your comment (unless he's to have a bot or keep a tab open for every question he's ever answered or commented on :D). I'd recommend Mark's set of pluralsight courses but also his blog - it's required reading anyway but if you want to filter there are lots of such examples there – Ruben Bartelink Aug 20 '15 at 16:19
  • Thanks Ruben. Hi @MarkSeemann, I have tried reading some of your blog posts about this, but I couldn't see any examples of how to do this, and I'm new to functional programming, could you point me at a relevant blog post / example / git hub / whatever? Thanks. – cedd Aug 24 '15 at 15:24
  • I did some more thinking about this and wrote this blog post which describes some techniques / refactorings that I find useful in siutations like this https://www.freecodecamp.org/news/how-to-test-complicated-calculations-new-refactoring/ – cedd Apr 07 '21 at 14:46

2 Answers2

2

Continuing the discussion from the comments to the OP, if you have referentially transparent functions, you can first test each small part by itself, and then combine them and test that the combination is correct.

Since constituent functions are referentially transparent, they are logically interchangeable with their return values. Now the only remaining step would be to prove that the overall function correctly composes the individual functions.

The is a great fit for property-based testing.

As an example, assume that you have two parts of a complex calculation:

module MyCalculations =
    let complexPart1 x y = x + y // Imagine it's more complex

    let complexPart2 x y = x - y // Imagine it's more complex

Both of these functions are deterministic, so assuming that you really want to test a facade function that composes these two functions, you can define this property:

open FsCheck.Xunit
open Swensen.Unquote
open MyCalculations

[<Property>]
let facadeReturnsCorrectResult (x : int) (y : int) =
    let actual = facade x y

    let expected = (x, y) ||> complexPart1 |> complexPart2 x
    expected =! actual

Like other property-based testing frameworks, FsCheck will throw lots of randomly generated values at facadeReturnsCorrectResult (100 times, by default).

Given that both complexPart1 and complexPart2 are deterministic, but you don't know what x and y are, the only way to pass the test is to implement the function correctly:

let facade x y = 
    let intermediateResult = complexPart1 x y
    complexPart2 x intermediateResult
Mark Seemann
  • 209,566
  • 41
  • 390
  • 671
  • Thanks @MarkSeemann. I had a go at that Diamond Kata from your blog once and it does look a lot easier / better using property-based testing. Anyway, getting back to the code above. I am happy testing like this in C#. It involves testing complexPart1 (&2) independently, and once done using them in the Assert phase when testing the class that does the composing. The downside in C# is that you have to make these methods public somehow. Is there some fundamental paradigm difference in functional programming that I am not aware of? – cedd Aug 27 '15 at 15:24
  • Another complication is that this example returns a simple type and only takes 2 parameters. Once you start composing many functions that return complex types and take a number of parameters I feel that tests would start becoming obscure quite quickly, although I've never tried this in a functional language. – cedd Aug 27 '15 at 15:26
  • You'd have to make the functions public in a Functional language as well, but what's the problem with that? If the functions have no side-effects, they're safe to call, so what's the problem with making them publicly available? – Mark Seemann Aug 27 '15 at 16:09
  • Fair point. I was guess I was trying to make it more black boxy and hide the details. – cedd Aug 27 '15 at 16:56
2

You need another abstraction level to make your methods simpler, so it will be easier to test them:

doStuff(trackConstructionType, referenceTrackWidth){
    ...
    trackCostMultipler = countTrackCostMultipler(trackConstructionType)
    countPilingCostPerArea = countPilingCostPerArea(referenceTrackWidth, trackCostMultipler)
    ...
}

countTrackCostMultipler(trackConstructionType){
    double trackCostMultipler;
    if (trackConstructionType = TrackConstructionType.Easy) trackCostMultipler = 0.8
    else if (trackConstructionType = TrackConstructionType.Normal) trackCostMultipler = 1
    else if (trackConstructionType = TrackConstructionType.Hard) trackCostMultipler = 1.3
    else throw new OutOfRangeException("Unknown TrackConstructionType: " + trackConstructionType.ToString());
    return trackCostMultipler;
}

countPilingCostPerArea(referenceTrackWidth, trackCostMultipler){
    return TrackCostPerMeter / referenceTrackWidth * trackCostMultipler;
}

Sorry for the code, I don't know the language, does not really matter...

If you don't want to make these methods public, then you have to move them to a separate class, and make them public there. The class name could be TrackCostMultiplerAlgorithm or ..Logic or ..Counter, or something like that. So you will be able to inject the algorithm into the higher abstraction level code if you'll have more different algorithms. Everything depends on the actual code.

Ohh and don't worry about the method and class lengths, if you really need a new method or class, because the code is too complex, then create one! Does not matter that it will be short. It will be always ease understanding as well, because you can write into the method name what it does. The code block inside the method only tells us how it does...

inf3rno
  • 20,735
  • 9
  • 97
  • 171
  • This is kind of what I do, but I find it hard to test this well. Something has to call CountPilingCostPerArea in the code above, and this needs to use the value calculated by countTrackCostMutiplier, so this interaction needs testing to make sure that the right method is called and then the return value of that is correctly passed. I've never been able to write an easy to understand test that does this for complicated situations. – cedd Aug 27 '15 at 15:30