In this series of posts I would like to display a very simple, though instructive, example of how easy refactoring works when you have both app and test code at your disposal. And at the same time I will show you the power of starting coding from specific and gradually moving to generic (and not the other way around).
But before “going wild” on this let me elaborate on a number of concepts that I will be using.
Test-Driven Development vs. test automation
As I also discuss in chapter 2 of my book, many times Test-Driven Development (TDD) and test automation (TA) are used like synonyms. Often I am told by a customer or a course attendee, that their team or company has started using TDD in their daily work. In most cases, inquiring a bit more into the details, it becomes apparent that it’s not so much TDD, but the fact that they have picked up creating automated test. So, more TA than TDD. Indeed TDD makes heavily use of TA, but is much more than that. It’s a programming technique that shapes your development practice. It prescribes how you get from a list of test – defining the behavior of the feature you’re building – to app and test code. Or rather: to test and app code, as in TDD test code is what you build first.
Whatever you think of TDD – you know some get allergic just hearing the term – one of the great things about it is that it’s “a game” with only two rules. Yep, just two rules, and everything else is derived from that.
- Never write a single line of code unless you have a failing automated test
- Eliminate duplication
Rule 1 directly relates to the term TDD: if you write a line, or more, of (app) code there should have been already a failing test available that urges you to write that code. I always image TDD to be a ladder where app code and test code make up the two rails and should not/cannot go too much “out of sync”.
Rule 2 triggers you to refactor your code, or, as the rule points it out, to eliminate any duplication that exists in your code. This rule helps you gradually move from “specific to generic” and is only applied once rule 1 has been fulfilled, not the other way around. Note that there is a valid wider interpretation of the second rule: clean up the code created so far – and nothing more – to make it readable, reusable and minimalistic.
Fresh fixture vs. shared fixture
As you might know from your own experience a bigger share of your time spent on testing and test coding is taken by setting up the data needed to be able to run your test(s). This data setup is also called (test) fixture and can be categorized in three groups or levels based on xUnit Patterns (see also chapter 5 of my book).
Applying this to Business Central (as I also discussed in Let’s talk about Shared Fixture and how to profit from this with the Dynamics NAV Test Toolkit):
- Prebuilt Fixture is the database we will run our tests on.
Typically this is CRONUS, containing all sorts of preset data.
- Lazy Setup or Shared Fixture is the data created at the start of a related number of tests.
Typically is about master, supplemental and setup data used jointly by a number of tests that reside in the same test codeunit.
- Fresh Setup or Fresh Fixture is data created for a specific test only.
So this is data added to the shared fixture needed to execute a specific test. This often entails transactional data and custom master data.
When developing an automated test one of your strives – a TDD attitude – should be to get it passing asap. Meaning that you are inclined to get data setup for that specific test – i.e. fresh fixture – at first not taking into account the broader context of related tests perse. Note that there is nothing holding you from reusing data creator functions that you have applied to previous test already if that gets your also passing asap. But … tend to keep it specific and once your test code and app code (change) result in a passing test, you apply rule 2 and can move from “specific to generic”. This can lead fresh fixture to be “promoted” to shared fixture. That’s what I am going to display in this series of posts.
From fresh to shared fixture – a refactoring example
In my TA courses and workshops I often make use of one of the examples that you can find in the Test-Automation-Examples GitHub repo: Blocking Deletion of Warehouse Shipment Line. This feature consist of two sub features, or so-called ATDD features:
- Unblock Deletion of Whse. Shpt. Line enabled
- Unblock Deletion of Whse. Shpt. Line disabled
Indeed two ATDD features that sound very much akin and probably lead to a number of tests that are also akin. If you have read my book you will know that, in my approach, each feature leads to a test codeunit containing the test functions that implement the scenarios that go with that feature. And if two test codeunits are somewhat similar they probably will have overlapping code parts, i.e. code duplication.
In this refactoring example I will start at the point where the scenarios of the first feature only have been implemented and not all duplication has been eliminated. We will first exercise rule 2 on this codeunit and then start working on the second feature. Doing the latter we and getting the test pass asap we will introduce another bunch of duplications.
Stay tuned for the next episode in this series.