My Authors
Read all threads
When I can't test it where it is, I look to move it somewhere else, where I *can* test it. Today's notion isn't so much a single refactoring as it is a strategy that can be achieved in different ways (and different multiple steps).
A modern and frequently occurring case: using a cool framework to expose service endpoints, we write a function and then we annotate it and *poof*, it's an endpoint. This is how Java+Swing works, or Python+Flask.
When we give that function a body, it will be a body of "our branching logic" (OBL). We've already discussed that OBL is the main target of our tests, so we definitely want to test that function. Sadly, testing that usually involves one or more of three owwies.
We'll have to either/or/and 1) fire up the app, 2) fire up the framework's test rig, 3) instantiate the class containing the method. Most of the time, none of these three choices is a fountain of geek joy.
The problem, in all three choices, is in the weight of that framework. And it's not a problem that's avoidable: we *chose* that framework precisely cuz it does so much for us. Of course it's heavy.
Now, be clear, this objection is not about it being "impossible" to do one of those three things. It's not. All three of them are doable. We're all geeks with mad skillz, and so were the framework writers. It's doable. It's just not wonderful.
And here's where my recurring theme pops up: I am the boss. The code works for me. I don't work for the code. If the code "forces" me to do one of those three unwonderful things, it's the code that's gonna change, not me.
So what's the answer? Conceptually, it's easy. (We'll deal with some in-practice complications in a second.) It's owwie to test the OBL code where it is, so let's put it somewhere else, call it from where it is, then we can test it in its new easier-to-test place.
The standard "someplace else" is just another class. We start with class MyFrameworkEndpoint and method doSomething() all annotated up, with a body of OBL. We end with two classes, MyFrameworkEndpoint and MyFrameworklessHandler, and the OBL is in the new class.
Notice that word "Frameworkless". That's important. What we're really doing is using two classes, one that uses framework-isms, and one that doesn't. The framework is what was forcing one of those three choices. It doesn't help us if the new class *also* uses framework-isms.
One way to understand MyFrameworkEndpoint is to understand it as a boundary, part of the rim. Everything outside the rim is framework-y. Everything inside the rim is framework-less. MFE's single responsibility is to make that translation.
Okay, the complications. Very often, the naive endpoint function freely intermixes framework code (FW) with our branching logic (OBL), something like this:

FW(); FW(); OBL(); FW(); OBL(); OBL(FW); FW(OBL);
The first trick: just re-order the code if you can. Put all the OBL code together, usually either at the end or in the middle. It's very often just that easy. The only reason it was intermixed was because we typed it in that order, not because the ordering was required.
Sometimes, OBL has to actually have an effect on the FW. At its simplest, we handle this by having the OBL return something the FW can use to have that effect.
If it's tricksier than that, we can use one of our dependency-inversion techniques, like observer, or callbacks, or lambdas, to make it happen. This happens every once in a while, but not so often as the naive endpoint code would make it seem.
There's one more complication we sometimes encounter: the FW has lots of clever gimcrackery to insert sneaky FW code into other classes, and we have to not write our code that way. (@autowired is often used this way.) The answer here is simple: "don't do that".
Anything I can cleverly inject into my frameworkless class I can also explicitly pass. I can even translate it to a frameworkless interface before I pass it.
At the risk of offending, I'll say that I don't use clever DI frameworks at all, even the ones that let you freely intermix with explicit passing. The reason is similar to my rationale for not using auto-mockers: it lets you do too many things one oughtn't do.
So we've looked at one case of moving code we want to test to someplace where it's easy to test, but this isn't a technique that's restricted to service endpoints, or even to framework environments.
Another framework setting first: apps with UI's nearly always use a framework. The problem is exactly analagous to the endpoint situation. I don't want to test it with the UI running. And the answer is the same: make OBL not use the framework, move it elsewhere, test it there.
Sometimes, the problem isn't a framework on top, it's a library on the bottom. Direct expressions of SQL in your code make that code untestable w/o a live and populated DB, for instance.
So here, the rim class is at the bottom, and its single responsibility isn't de-frameworking but de-databasing the interactions.
Such approaches usually work by returning answers as generic or custom containers. If those containers can be hand-loaded instead of DB'd, then we can test our OBL without having a database handy.
Now, there's one more, very very simple case of this, involving neither frameworks nor libraries.
The single most common question from would-be TDD'ers in their early efforts: how do I test complex private methods?

Do you see the answer? Put them someplace where they're not private and test them there.
In the Before, we have a.public() calling a.private(). In the After, we have a.public() calling it's private member of type B's b.public(). The OBL is in b.public(), where we can test it to our heart's content. Clients of A have no idea that such functionality is even available.
So you can see what I mean now when I says today's idea isn't really a refactoring. It's a strategy, with a variety of tactical approaches to accomplish it.
By way of calling out themes: 1) The code works for you, you don't work for the code. 2) Most refactoring is about re-arranging, not eliminating. 3) The most important code to test is "our branching logic". 4) Don't let anything keep you from testing what you want to test.
I'm out, have fun. The pitch: if you like my content, these muses become blogs & podcasts, and you can subscribe to them, free, and spam-free, so you never miss one.

Subscribe today!

geepawhill.org
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with GeePaw Hill

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!