00:00:03.920
Thank you.
00:00:10.920
Are we up here? There we go. All right, it's good to be back.
00:00:17.220
I'm in Roswell again. I think last time I was here it was 2018, in March.
00:00:24.300
March in Poland can still be frigid; it was incredibly cold and reminded me of growing up in Alaska.
00:00:29.580
And I'm not joking.
00:00:36.000
The first thing I’d like to get out of the way is that this isn’t a sales pitch.
00:00:42.180
I am the author of Test Bench, and yes, I believe it’s worth checking out.
00:00:47.879
I will be introducing it to you, but it's a very simple tool and its value may not be immediately apparent.
00:00:54.780
The code snippet I'm going to show you is trivial enough to give you the gist of it, and it won't look much different from other frameworks.
00:01:02.280
I’m going to interleave both the history of Test Bench and my own experience developing it in this presentation.
00:01:09.299
If I wanted to title the story of Test Bench, I might start with how I learned TDD the hard way.
00:01:14.820
But 'hard' doesn't even begin to describe it.
00:01:21.119
A better way to phrase it would be how I learned TDD in the most excruciating, agonizing, painful way imaginable.
00:01:26.820
Some of you who have known me for a few years might have an idea of where this is headed.
00:01:33.380
Test Bench and TDD are inextricable.
00:01:40.140
What makes Test Bench unique is that it was produced through the process of Test Driven Development.
00:01:47.700
Remember, XUnit frameworks emerged even before I learned to code.
00:01:53.579
Tools for test automation predate TDD.
00:02:00.180
When I say that what makes Test Bench unique is that it was produced by TDD, I mean something novel here.
00:02:07.439
It doesn't mean that I wrote tests first, although I did; it also doesn't mean I did Red-Green-Refactor, although that happened too.
00:02:13.800
It’s about validating every step of the way through the lens of our test automation.
00:02:19.860
A little bit about me: I’ll dive into some details about myself here, as it's part of communicating a central point.
00:02:25.379
I am one of those '90s kids who grew up with a computer and BASIC.
00:02:31.560
If anyone recalls this time, we’d get magazines with little video games whose source code was printed in the back.
00:02:37.160
I learned to program by transcribing line-for-line the code from the magazines into QBasic.
00:02:42.840
That's kind of how I got started.
00:02:48.239
As a side note, I also started teaching myself guitar, and I was pretty good for a self-taught amateur.
00:02:55.980
However, I really couldn't keep time; if I'd gotten a teacher, they would have put me on a metronome.
00:03:03.959
This consistent theme reflects a significant limitation of mine: my temptation to rush.
00:03:09.739
Even right now, I feel like just blasting through this presentation.
00:03:18.780
But yes, I have to take a beat.
00:03:26.099
Motivating myself for school was tough; if it wasn't fun, I wasn't really going to do it.
00:03:32.340
I was really more into video games and fantasy novels.
00:03:39.060
I wanted to be a hard worker, but it just didn’t happen.
00:03:46.680
I dropped out of university but soon found a job in programming.
00:03:52.200
Actually, I was quite good at that job, though I didn’t realize it at the time.
00:03:59.519
This isn't an endorsement of my abilities; I stumbled into a very favorable working condition by luck.
00:04:07.140
Working as a programmer suited my strengths and largely ignored my weaknesses and limitations.
00:04:13.860
I was good at getting code to work, and that's all I really needed to find gainful employment.
00:04:20.100
I enjoyed early success, and unfortunately, that led to a pretty big ego.
00:04:26.340
I grew overconfident, believing I understood everything about programming.
00:04:31.500
I started out developing low-level device drivers for Linux, and I didn't learn web development until much later.
00:04:38.160
Five years into my career, around 2008 or 2009, I still had no idea that I was consistently rushing my implementations.
00:04:45.960
Not only was my work rushed, but my understanding of fundamental software concepts was makeshift at best.
00:04:52.080
By the time Scott and I started working together in 2015, if you'd asked me what TDD was, I'd have assured you I knew.
00:04:59.759
I was well versed in it; it was a popular topic, after all.
00:05:06.960
You write your tests, make the tests pass, and then refactor the code until it’s better.
00:05:12.900
That's the gist of it, right? The problem is, that’s not really what TDD is about.
00:05:20.100
That's just a routine procedure; it doesn't state the objective.
00:05:26.520
I'll go into this more, but suffice it to say I simply didn't understand the underlying premise of TDD or software design.
00:05:33.240
Not nearly as much as I thought I did, at least.
00:05:40.140
When Scott and I started working together in 2015, I think working with him was a wonderful experience.
00:05:46.680
It was particularly valuable if you're unknowingly harboring misunderstandings about fundamental software principles.
00:05:53.280
We were working on a project together and had some back-and-forth about choosing our test framework.
00:06:01.320
At that time, I was a devoted MiniTest user, and I didn’t really understand the excitement around RSpec.
00:06:07.200
I was used to testing with MiniTest, and at that time, RSpec had transitioned its syntax.
00:06:14.160
Scott pointed out the differences between 'must equal' and 'should equal', stating these aren't semantically equivalent.
00:06:19.860
I accepted his point, even if I didn't fully grasp its importance at the time.
00:06:26.400
This dissatisfaction pushed us to consider how our test framework should operate.
00:06:32.700
Wouldn't it just be nice if our test files were simply scripts?
00:06:38.640
Ultimately, automated tests are just procedures. Why not write procedural code?
00:06:45.960
We want to run them with Ruby without needing elaborate assertions.
00:06:52.560
For us, logging outputs every value that goes through our objects was sufficient.
00:06:59.880
We didn't require anything particularly elaborate.
00:07:07.140
We just needed the ability to establish contexts and write a little prose.
00:07:14.400
The tests would include assert and refute methods within each block.
00:07:21.240
There’s nothing complicated about this, and as a result, this was fairly simple to script.
00:07:28.560
This is what Test Bench looks like—very simple.
00:07:38.640
The output shows the context and tests in your test, reflecting the structure.
00:07:45.000
I sketched an initial bootstrap implementation. It's essentially assert, refute, context, and tests.
00:07:50.580
This was released on March 31st, 2016.
00:07:57.420
After that, we transitioned all our projects at work over to Test Bench from MiniTest.
00:08:05.520
Even in its limited early form, our experience with Test Bench was equivalent to what we had with MiniTest.
00:08:12.420
Several years later, in late 2018, I decided to undertake a rewrite of Test Bench.
00:08:19.500
This culminated in its official V1 release and necessitated the use of TDD.
00:08:27.600
I realized TDD was fundamentally about improving the quality of designs and implementations.
00:08:35.040
However, I found that my implementations at work were consistently not good enough.
00:08:42.240
We would go back and forth about glaring mistakes that I often missed until they were pointed out.
00:08:48.840
The common cause was rushing; the first 20 percent of my efforts were enjoyable.
00:08:55.140
I would declare the work done once I got it working, without doing the refactoring step of Red-Green-Refactor.
00:09:02.640
Instead, I merely rearranged code in my editor, thinking it was refactoring.
00:09:09.420
This wasn’t fun; meticulously spotting mistakes and correcting them was not my strong suit.
00:09:16.680
A common theme for me was running afoul of design principles, and I grew tired of brutal code reviews.
00:09:23.280
I wanted to learn how to get implementations right the first time.
00:09:30.480
To achieve this, I needed to stop making so many mistakes.
00:09:37.640
Scott and I often discussed how a mistake isn't merely a defect; rather, it obstructs our understanding.
00:09:45.300
We strive for implementations with no obstructions to understanding.
00:09:52.560
I established some ground rules for myself. I would devote my spare time to re-implementing Test Bench.
00:09:59.280
I committed to a process that would yield better results and help me practice working at a higher level of implementation quality.
00:10:05.760
The motivation behind this was to eliminate design mistakes caused by rushing.
00:10:11.520
I also decided to apply youthful objects in my own project.
00:10:19.380
The previous incarnation of Test Bench was written in a way I had learned before collaborating with Scott.
00:10:26.880
I set uncompromising design principles for myself this time.
00:10:33.480
If I encountered a mistake or discovered one along the way, I would correct it immediately.
00:10:40.620
When working with numerous objects, one mistake could ripple through the implementation.
00:10:47.520
I agreed to correct any fallout caused by the original mistake.
00:10:55.080
If I ever felt out of control of the implementation, I would start over completely.
00:11:02.760
To prevent total loss, I would set up checkpoints—known good positions in the implementation.
00:11:09.840
I had to trust the process.
00:11:16.440
What I’m describing may come off as obsessive-compulsive behavior, and I don’t recommend it.
00:11:22.880
However, if we trust design principles and follow a meticulous process, we could avoid the pitfalls I faced.
00:11:30.000
Constantly challenging ourselves to understand our work better is crucial.
00:11:36.300
Initially, I was wiping the project clean frequently, leading to discouragement.
00:11:42.960
So, I started using Git to create checkpoints, reworking my approach.
00:11:50.520
I structured my Git history so that the earliest commits in my branch were crucial inner-core classes.
00:11:57.480
This allowed me to leverage interactive rebasing.
00:12:05.160
If I found a mistake in one of those core classes, I could backtrack.
00:12:12.720
I could then run tests to ensure each new class I introduced maintained a known good state.
00:12:18.780
The process forced me to see the value of encapsulation more clearly.
00:12:25.320
It made me realize the importance of providing everything needed for downstream users of a class.
00:12:31.680
I realized I was in over my head when I diverted focus from Test Bench to develop a framework for terminal output styling.
00:12:38.040
Unfortunately, my journals from that period are a mess.
00:12:45.000
This Test Bench rewrite had become an overwhelming second and third job for me.
00:12:52.440
Substance abuse was also an issue at that time.
00:12:59.460
This culminated in a bizarre turn of events.
00:13:05.640
If you wish to avoid gruesome details, now would be the time to look away.
00:13:12.360
I experienced a complete nervous breakdown and fell off a balcony four stories down.
00:13:20.400
I sustained seven broken ribs, a broken sternum, a broken clavicle, a broken femur, and a broken face.
00:13:27.300
My injuries also included a broken nose and a Lefort 3 fracture.
00:13:34.200
During this time, Ethan Garofalo, who presented at WrocLove, coordinated a wonderful video for me.
00:13:40.320
This support was incredibly motivating, and I genuinely appreciate it.
00:13:47.760
After being discharged from the hospital, I returned to Test Bench with a fresh perspective.
00:13:54.540
Surprisingly, I found that the implementation quality was decent.
00:14:01.140
I reflected on the code I’d written six months prior and considered it acceptable.
00:14:06.840
From an industrial perspective, nobody could justify my extremely slow pace of work.
00:14:13.740
However, with my approach and patience, I had improved my ability to tackle new problems.
00:14:20.640
I credit Scott for teaching me that in everything we do, we're either improving or settling for less.
00:14:26.880
The biggest opportunity for improvement lies in practicing at a higher level.
00:14:34.560
Slowing down and approaching every line of code as an opportunity for improvement is essential.
00:14:40.560
That's it.
00:14:49.680
Any questions?
00:14:53.760
You kept mentioning fixtures or specific terminology that deserve clarification.
00:15:01.920
If you say 'fixture' from the Test Bench perspective and you're a Rails developer, you might not draw the same conclusion.
00:15:07.560
Would you clarify what we mean by that?
00:15:13.400
Certainly, I skipped the presenter notes meant to address this.
00:15:20.520
Earlier in the presentation, I demonstrated that you could integrate the Test Bench DSL into a class.
00:15:26.880
This revealed a new capability we had for implementing test objects.
00:15:32.040
The term 'fixture' arose from a lengthy debate about what to name this module.
00:15:41.040
Ultimately, despite its coincidence with Rails, we maintained the term 'fixture.'
00:15:47.280
Web applications often require specific data to be loaded into the database, which has altered the term's meaning.
00:15:54.840
Historically, 'fixture' referred to a device for testing something. It's behavioral, not data-driven.
00:16:07.320
Back in 1989, Kent Beck introduced the concept while discussing XUnit in Smalltalk.
00:16:13.560
Fixture has always been about the testing aspect; however, Rails adopted the term for data.
00:16:19.680
This has caused some ambiguity that we’re introducing with Test Bench.
00:16:25.920
All I can say is if you find a better term than 'Test Bench fixture', I'm all ears.
00:16:31.920
A helpful analogy is comparing a fixture to a light fixture.
00:16:38.040
A light fixture holds and powers a lightbulb; similarly, a test object holds the methods that test other objects.
00:16:44.640
This is the story behind the Test Bench fixture.
00:16:52.560
Thank you for calling that out, Scott.
00:16:59.520
When writing tests, I often want to assert not merely literal outputs but some properties of outputs.
00:17:06.360
For example, I may want to assert that an array contains a hash with a specific key.
00:17:12.840
I prefer RSpec’s composable style in such cases.
00:17:20.040
How do we implement such tests with the relatively small API of Test Bench?
00:17:25.740
That's a great question! In a current project, we integrate with third-party APIs.
00:17:33.120
We normally regard objects emitting large sets of primitive data as suspicious.
00:17:40.080
The 'tell, don't ask' principle guides this view; we don’t want to receive excessive data unless absolutely necessary.
00:17:47.520
In our current project, we work with bizarre and complex data structures from APIs.
00:17:54.180
We’ve implemented fixtures allowing us to assert elements and other properties specifically.
00:18:00.780
We can compare individual attributes of a JSON response, for example.
00:18:08.040
Using Nokogiri and CSS selectors, we can assert specific XML elements.
00:18:14.700
This direction aims to specialize our fixtures within Test Bench.
00:18:22.080
Instead of relying on generic outputs, we can build tailored assertions for our needs.
00:18:29.280
Fixtures can significantly simplify the process.
00:18:36.900
To conclude, fixtures are to Test Bench what matchers are to RSpec.
00:18:43.320
Despite some unfortunate history in Ruby, we aim for clarity in our testing approach.
00:18:50.880
A fixture is simply a class, just like any other class.
00:18:57.600
By including the Test Bench module, we gain access to all features.
00:19:03.840
It can perform the necessary assertions and organize our tests neatly.
00:19:11.160
In essence, we can structure our tests in a nested fashion that simplifies assertions.
00:19:17.520
Our focus should be on fundamental principles rather than the complexity of the framework.
00:19:23.880
With Test Bench, the aim is to eliminate needless complications and focus right on what you need.
00:19:30.240
This way, we can emphasize simplicity in our designs.
00:19:37.320
As we develop, we must always consider best practices and how we can systematically enhance our code.
00:19:44.520
Despite years of development, there is still much room for improvement.
00:19:50.520
Indeed, software is never 'done'; it continually awaits future enhancements.
00:19:57.920
The practice of refining software never ends, which is an important lesson from my experience.
00:20:05.400
Perfect practice, as my high school soccer coach used to say, is key.
00:20:12.480
Practice alone makes permanent, not perfect.
00:20:20.040
The biggest takeaway from developing Test Bench is that everything you do enhances your skills.
00:20:26.000
When we tackle coding challenges, we should do our best to improve.
00:20:32.760
It is essential to view every line of code as an opportunity for improvement.
00:20:39.420
That's it.
00:20:51.000
Thank you, Nathan.
00:20:56.760
Any questions?