One Deceptively Simple Question
...to Unlock Testability, Better Design, and TDD
Derek Lee @thextremeprogrammer
Software Testing Meetup | April 16, 2025
1/149
Derek Lee @theextremeprogrammer
→ Music Software EdTech Innovator/Founder
→ Specializes in XP, TDD, Pair Pro, Mobile Dev
→ Passionate about refactoring, OODP, SOLID
→ Experienced in lean product UI/UX design/mgmt
→ 25+ years software dev
→ 35+ years drumming
2/149
3/149
Drum Notation by Beat Note
4/149
Drum Notation by Beat Note
5/149
!
25/149
!
Goals For This Talk
I want to help you:
→ Unlock Testability
→ Unlock Better Design
→ Unlock TDD
26/149
!
27/149
!
Anti-Goals For This Talk
If I had more time I might, but since I don't, I won't:
→ Teach TDD directly
→ Prescribe a testing order
→ and lots, lots more...
28/149
!
Anti-Goals For This Talk
If I had more time I might, but since I don't, I won't:
→ Teach TDD directly
→ Prescribe a testing order
→ and lots, lots more...
(... though I have strong, yet loosely-held, opinions on these! )
29/149
Example #1
30/149
We're building a back-
end HTTP API for a book
store that specializes in
software testing books.
31/149
Let's look at the implementation code first.
Why?
!
Before we can become comfortable with testing,
or a test-first approach,
it can often be helpful to
spike on and understand
the implementation code first.
32/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
33/149
Example #1 [Test] | Java Spring
public class BooksControllerTest {
@Test
public void getBooks_returnsBook() {
BooksController booksController = new BooksController();
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build();
mockMvc.perform(get("/api/books"))
.andExpect(jsonPath("$[0].id", equalTo(1)))
.andExpect(jsonPath("$[0].name", equalTo("TDD by Example")))
.andExpect(jsonPath("$[0].author", equalTo("Kent Beck")))
;
}
34/149
Back to the implementation code...
38/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
39/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
Q: What assumptions are baked into this code?
40/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
Q: How can we adapt this to new behavior?
41/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
Q: How reusable is this code as-is?
42/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
Q: Does anything here feel fragile?
43/149
!
These are good questions to ask when looking at
code that's difficult to test:
→ What assumptions are baked into this code?
→ How can we adapt this to new behavior?
→ How reusable is this code as-is?
→ Does anything here feel fragile?
44/149
Which leads us to a
deceptively simple
question...
45/149
Mind you...
46/149
Not the only deceptively
simple question, but...
47/149
At least one deceptively
simple question
48/149
(that I like to ask)
49/149
(there may be others...)
50/149
!
51/149
"What
52/149
is
53/149
this code
54/149
dependent
55/149
on?"
56/149
"What is this code
dependent on?"
57/149
"What is this code
dependent on?"
59/149
Example #1 [Impl] | Java Spring
@RestController
@RequestMapping("/api/books")
public class BooksController {
@GetMapping()
public List<Book> getBooks() {
Book tddBook = new Book(1L, "TDD by Example", "Kent Beck");
return singletonList(tddBook);
}
...
Q: What is this code dependent upon?
60/149
How do we make this
code
more testable?
62/149
Improve the design?
63/149
Drive from the tests?
64/149
We need to be able to
control the data that is
returned from the
controller in the tests.
65/149
Option A: Inject the data from the test
public class BooksControllerTest {
@Test
public void getBooks_returnsBook() {
BooksController booksController = new BooksController(
singletonList(new Book(1L, "TDD by Example", "Kent Beck"))
);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build();
mockMvc.perform(get("/api/books"));
...
66/149
This is data injection.
67/149
Option B: Inject an object we can stub
which provides the data
public class BooksControllerTest {
@Test
public void getBooks_returnsBook() {
StubBooksRepository stubBooksRepository = new StubBooksRepository();
BooksController booksController = new BooksController(stubBooksRepository);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build();
stubBooksRepository.setGetAll_returnValue(
singletonList(new Book(1L, "TDD by Example", "Kent Beck"))
);
mockMvc.perform(get("/api/books"));
...
69/149
This is
dependency injection.
70/149
Option C: ....
There are likely other options that could be
considered, but...
72/149
I want to learn more!
See Kent Beck's TDD by Example book:
→ Hard-coding return values = "TDD Green Bar
Pattern" Fake It Till You Make It
→ Refactoring to use a stub = "TDD Green Bar
Pattern" Triangulation
74/149
I want to learn more!
→ Data Injection
→ Dependency Injection
→ Test Doubles: Stubs and many more!
(important: stubs are not "mocks"! )
75/149
Example #2
76/149
We're building
business logic
for an
airline travel app.
77/149
Remember,
we'll look at the
implementation code
first.
78/149
!
Before we can become
comfortable with testing,
or a test-first approach,
it can often be helpful to
spike on and understand
the implementation code first.
79/149
Example #2 [Impl] | JavaScript
function calculateMinutesUntilDeparture(departureTime) {
const now = new Date();
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE
);
}
80/149
Example #2 [Test] | JavaScript
test("calculateMinutesUntilDeparture returns the correct number of minutes", () => {
const departureTime = new Date("2025-04-16T12:00:00Z");
const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime);
expect(minutesUntilDeparture).toBe(60);
});
81/149
Example #2 [Test] | JavaScript
test("calculateMinutesUntilDeparture returns the correct number of minutes", () => {
const departureTime = new Date("2025-04-16T12:00:00Z");
const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime);
expect(minutesUntilDeparture).toBe(60);
});
When does this test pass?
82/149
Example #2 [Test] | JavaScript
test("calculateMinutesUntilDeparture returns the correct number of minutes", () => {
const departureTime = new Date("2025-04-16T12:00:00Z");
const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime);
expect(minutesUntilDeparture).toBe(60);
});
When does this test fail?
83/149
Back to the implementation code...
84/149
Example #2 [Impl] | JavaScript
function calculateMinutesUntilDeparture(departureTime) {
const now = new Date();
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE
);
}
85/149
What's a deceptively
simple question we could
ask here?
86/149
"What is this code
dependent on?"
88/149
"What is this code
dependent on?"
89/149
Example #2 [Impl] | JavaScript
function calculateMinutesUntilDeparture(departureTime) {
const now = new Date();
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE
);
}
"What is this code dependent on?"
90/149
!
Testing becomes
difficult when code
depends on real-world
time.
91/149
!
If you encounter difficulty testing
something, that's usually an indicator that
it may be dependent on an object you
don't yet control.
92/149
What do we need to be
able to control
from the tests?
(in this case)
93/149
Example #2 [Impl] | JavaScript
function calculateMinutesUntilDeparture(departureTime) {
const now = new Date();
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE
);
}
94/149
Example #2 [Impl] | JavaScript
function calculateMinutesUntilDeparture(departureTime) {
const now = new Date();
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE
);
}
The current date/time.
95/149
Option A: Inject the current date/time from the test
function calculateMinutesUntilDeparture(departureTime, currentTime) {
const MILLISECONDS_PER_MINUTE = 1000 * 60;
return Math.floor(
(departureTime.getTime() - currentTime.getTime()) / MILLISECONDS_PER_MINUTE
);
}
96/149
(once again)
This is data injection.
97/149
Option B: Inject an object we can stub
which provides the current date/time
class DepartureService {
constructor(dateProvider) {
this.dateProvider = dateProvider;
this.MILLISECONDS_PER_MINUTE = 1000 * 60;
}
calculateMinutesUntilDeparture(departureTime) {
const currentTime = this.dateProvider.now();
return Math.floor(
(departureTime.getTime() - currentTime.getTime()) / this.MILLISECONDS_PER_MINUTE
);
}
}
const realDateProvider = { now: () => new Date() };
99/149
(once again)
This is
dependency injection.
100/149
Option C: ....
There are likely other options that could be
considered, but...
102/149
I want to learn more!
See my slides about the Date Provider pattern:
https://www.slideshare.net/DerekLee/standing-the-
test-of-time-the-date-provider-pattern
104/149
Example #3
105/149
We're building the
infrastructure
for a
front end mobile app
to communicate with the
back-end HTTP API.
106/149
Remember,
we'll look at the
implementation code
first.
107/149
!
Before we can become
comfortable with testing,
or a test-first approach,
it can often be helpful to
spike on and understand
the implementation code first.
108/149
Example #3 [Impl] | Swift iOS
import Foundation
struct NetworkHttp {
let baseServerUrl: String
func get(endpoint: String) async throws -> Data {
let urlString = baseServerUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
109/149
Example #3 [Test] | Swift iOS
import XCTest
final class NetworkHttpTests: XCTestCase {
func test_get_performsRequestAndReturnsData() async throws {
let networkHttp = NetworkHttp(baseUrl: "https://example.com")
let data = try await networkHttp.get(endpoint: "/test")
XCTAssertFalse(data.isEmpty)
}
}
110/149
What are some of the
challenges with this
testing approach?
111/149
This test depends on
real-world conditions.
If the server is down or the endpoint is
invalid, this test will fail.
112/149
It's not deterministic.
You can't control the response.
You're at the mercy of
whatever is at that URL.
113/149
You can't simulate errors.
What if you wanted to test how your code
handles a timeout or bad response?
114/149
Flaky!
Fail on bad network
115/149
Long-running tests!
Network latency
116/149
Environment Instability!
"Works on my machine"
117/149
Back to the implementation code...
118/149
Example #3 [Impl] | Swift iOS
import Foundation
struct NetworkHttp {
let baseServerUrl: String
func get(endpoint: String) async throws -> Data {
let urlString = baseServerUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
119/149
What's a deceptively
simple question we could
ask here?
120/149
"What is this code
dependent on?"
122/149
"What is this code
dependent on?"
123/149
Example #3 [Impl] | Swift iOS
import Foundation
struct NetworkHttp {
let baseServerUrl: String
func get(endpoint: String) async throws -> Data {
let urlString = baseServerUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
"What is this code dependent on?"
124/149
!
There's a hint at the top of the code...
anytime you see import it's a clue that
there's a dependency.
This was more obvious in Obj-C because imports were more
detailed and not at the module level, but still... this is still a good
hint.
125/149
Example #3 [Impl] | Swift iOS
import Foundation
struct NetworkHttp {
let baseServerUrl: String
func get(endpoint: String) async throws -> Data {
let urlString = baseServerUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
"What is this code dependent on?"
126/149
Example #3 [Impl] | Swift iOS
import Foundation
struct NetworkHttp {
let baseServerUrl: String
func get(endpoint: String) async throws -> Data {
let urlString = baseServerUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
"What is this code dependent on?"
127/149
So how do we make this
code more testable?
128/149
There's a lot to this so I'll give you the
overview and you can check the details
later.
129/149
Example #3 [Impl v2] | Swift iOS
import Foundation
protocol NetworkSession {
func data(from url: URL) async throws -> (Data, URLResponse)
}
130/149
Example #3 [Test v2] | Swift iOS
import XCTest
final class NetworkHttpTests: XCTestCase {
func test_get_returnsStubbedData() async throws {
let expectedData = "Hello, world!".data(using: .utf8)!
let stubHttpUrlResponse = HTTPURLResponse(
url: URL(string: "https://example.com")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)!
let stubSession = StubNetworkSession(data: expectedData, response: stubHttpUrlResponse)
let networkHttp = NetworkHttp(baseUrl: "https://example.com", session: stubSession)
let resultData = try await networkHttp.get(endpoint: "/test")
XCTAssertEqual(resultData, "Hello, world!".data(using: .utf8)!)
}
}
131/149
Example #3 [Stub] | Swift iOS
final class StubNetworkSession: NetworkSession {
let stubbedData: Data
let stubbedResponse: URLResponse
init(data: Data, response: URLResponse) {
self.stubbedData = data
self.stubbedResponse = response
}
func data(from url: URL) async throws -> (Data, URLResponse) {
return (stubbedData, stubbedResponse)
}
}
132/149
Example #3 [Impl v2] | Swift iOS
extension URLSession: NetworkSession {}
133/149
Example #3 [Impl v2] | Swift iOS
struct NetworkHttp {
let baseUrl: String
let session: NetworkSession
func get(endpoint: String) async throws -> Data {
let urlString = baseUrl + endpoint
let url = URL(string: urlString)!
let (data, _) = try await session.data(from: url)
return data
}
}
134/149
Now our test is...
135/149
Fast
136/149
Fast
Deterministic
137/149
Fast
Deterministic
Doesn't touch the network
138/149
Fast
Deterministic
Doesn't touch the network
Focused on our own logic
139/149
!
140/149
Q&A
141/149
Let's review...
142/149
!
Goals For This Talk
→ Help you see dependencies more easily
→ Sharpen your design and testing instincts
→ Create an “aha!” moment with one clear,
repeatable, memorable question:
"What is this code dependent on?"
144/149
!
Anti-Goals For This Talk
→ Teach TDD directly
→ Prescribe a testing order
→ and lots, lots more...
(... though I have strong, yet loosely-held, opinions on these! )
146/149
How did I do?
https://forms.gle/X18Wmspm5y2TyCfA7
Please share your feedback!
147/149
Thank you!
Tokyo Software Testing Meetup!
Attendees, Organizers,
Presenters, Le Wagon!
148/149
!
Thank you!
Find me online: @theextremeprogrammer
https://artandscienceofcoding.com/
149/149

One Deceptively Simple Question to Unlock Testability, Better Design, and TDD

  • 1.
    One Deceptively SimpleQuestion ...to Unlock Testability, Better Design, and TDD Derek Lee @thextremeprogrammer Software Testing Meetup | April 16, 2025 1/149
  • 2.
    Derek Lee @theextremeprogrammer →Music Software EdTech Innovator/Founder → Specializes in XP, TDD, Pair Pro, Mobile Dev → Passionate about refactoring, OODP, SOLID → Experienced in lean product UI/UX design/mgmt → 25+ years software dev → 35+ years drumming 2/149
  • 3.
  • 4.
    Drum Notation byBeat Note 4/149
  • 5.
    Drum Notation byBeat Note 5/149
  • 6.
  • 7.
    ! Goals For ThisTalk I want to help you: → Unlock Testability → Unlock Better Design → Unlock TDD 26/149
  • 8.
  • 9.
    ! Anti-Goals For ThisTalk If I had more time I might, but since I don't, I won't: → Teach TDD directly → Prescribe a testing order → and lots, lots more... 28/149
  • 10.
    ! Anti-Goals For ThisTalk If I had more time I might, but since I don't, I won't: → Teach TDD directly → Prescribe a testing order → and lots, lots more... (... though I have strong, yet loosely-held, opinions on these! ) 29/149
  • 11.
  • 12.
    We're building aback- end HTTP API for a book store that specializes in software testing books. 31/149
  • 13.
    Let's look atthe implementation code first. Why? ! Before we can become comfortable with testing, or a test-first approach, it can often be helpful to spike on and understand the implementation code first. 32/149
  • 14.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... 33/149
  • 15.
    Example #1 [Test]| Java Spring public class BooksControllerTest { @Test public void getBooks_returnsBook() { BooksController booksController = new BooksController(); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build(); mockMvc.perform(get("/api/books")) .andExpect(jsonPath("$[0].id", equalTo(1))) .andExpect(jsonPath("$[0].name", equalTo("TDD by Example"))) .andExpect(jsonPath("$[0].author", equalTo("Kent Beck"))) ; } 34/149
  • 16.
    Back to theimplementation code... 38/149
  • 17.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... 39/149
  • 18.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... Q: What assumptions are baked into this code? 40/149
  • 19.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... Q: How can we adapt this to new behavior? 41/149
  • 20.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... Q: How reusable is this code as-is? 42/149
  • 21.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... Q: Does anything here feel fragile? 43/149
  • 22.
    ! These are goodquestions to ask when looking at code that's difficult to test: → What assumptions are baked into this code? → How can we adapt this to new behavior? → How reusable is this code as-is? → Does anything here feel fragile? 44/149
  • 23.
    Which leads usto a deceptively simple question... 45/149
  • 24.
  • 25.
    Not the onlydeceptively simple question, but... 47/149
  • 26.
    At least onedeceptively simple question 48/149
  • 27.
    (that I liketo ask) 49/149
  • 28.
    (there may beothers...) 50/149
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
    "What is thiscode dependent on?" 57/149
  • 36.
    "What is thiscode dependent on?" 59/149
  • 37.
    Example #1 [Impl]| Java Spring @RestController @RequestMapping("/api/books") public class BooksController { @GetMapping() public List<Book> getBooks() { Book tddBook = new Book(1L, "TDD by Example", "Kent Beck"); return singletonList(tddBook); } ... Q: What is this code dependent upon? 60/149
  • 38.
    How do wemake this code more testable? 62/149
  • 39.
  • 40.
    Drive from thetests? 64/149
  • 41.
    We need tobe able to control the data that is returned from the controller in the tests. 65/149
  • 42.
    Option A: Injectthe data from the test public class BooksControllerTest { @Test public void getBooks_returnsBook() { BooksController booksController = new BooksController( singletonList(new Book(1L, "TDD by Example", "Kent Beck")) ); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build(); mockMvc.perform(get("/api/books")); ... 66/149
  • 43.
    This is datainjection. 67/149
  • 44.
    Option B: Injectan object we can stub which provides the data public class BooksControllerTest { @Test public void getBooks_returnsBook() { StubBooksRepository stubBooksRepository = new StubBooksRepository(); BooksController booksController = new BooksController(stubBooksRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(booksController).build(); stubBooksRepository.setGetAll_returnValue( singletonList(new Book(1L, "TDD by Example", "Kent Beck")) ); mockMvc.perform(get("/api/books")); ... 69/149
  • 45.
  • 46.
    Option C: .... Thereare likely other options that could be considered, but... 72/149
  • 47.
    I want tolearn more! See Kent Beck's TDD by Example book: → Hard-coding return values = "TDD Green Bar Pattern" Fake It Till You Make It → Refactoring to use a stub = "TDD Green Bar Pattern" Triangulation 74/149
  • 48.
    I want tolearn more! → Data Injection → Dependency Injection → Test Doubles: Stubs and many more! (important: stubs are not "mocks"! ) 75/149
  • 49.
  • 50.
    We're building business logic foran airline travel app. 77/149
  • 51.
    Remember, we'll look atthe implementation code first. 78/149
  • 52.
    ! Before we canbecome comfortable with testing, or a test-first approach, it can often be helpful to spike on and understand the implementation code first. 79/149
  • 53.
    Example #2 [Impl]| JavaScript function calculateMinutesUntilDeparture(departureTime) { const now = new Date(); const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE ); } 80/149
  • 54.
    Example #2 [Test]| JavaScript test("calculateMinutesUntilDeparture returns the correct number of minutes", () => { const departureTime = new Date("2025-04-16T12:00:00Z"); const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime); expect(minutesUntilDeparture).toBe(60); }); 81/149
  • 55.
    Example #2 [Test]| JavaScript test("calculateMinutesUntilDeparture returns the correct number of minutes", () => { const departureTime = new Date("2025-04-16T12:00:00Z"); const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime); expect(minutesUntilDeparture).toBe(60); }); When does this test pass? 82/149
  • 56.
    Example #2 [Test]| JavaScript test("calculateMinutesUntilDeparture returns the correct number of minutes", () => { const departureTime = new Date("2025-04-16T12:00:00Z"); const minutesUntilDeparture = calculateMinutesUntilDeparture(departureTime); expect(minutesUntilDeparture).toBe(60); }); When does this test fail? 83/149
  • 57.
    Back to theimplementation code... 84/149
  • 58.
    Example #2 [Impl]| JavaScript function calculateMinutesUntilDeparture(departureTime) { const now = new Date(); const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE ); } 85/149
  • 59.
    What's a deceptively simplequestion we could ask here? 86/149
  • 60.
    "What is thiscode dependent on?" 88/149
  • 61.
    "What is thiscode dependent on?" 89/149
  • 62.
    Example #2 [Impl]| JavaScript function calculateMinutesUntilDeparture(departureTime) { const now = new Date(); const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE ); } "What is this code dependent on?" 90/149
  • 63.
    ! Testing becomes difficult whencode depends on real-world time. 91/149
  • 64.
    ! If you encounterdifficulty testing something, that's usually an indicator that it may be dependent on an object you don't yet control. 92/149
  • 65.
    What do weneed to be able to control from the tests? (in this case) 93/149
  • 66.
    Example #2 [Impl]| JavaScript function calculateMinutesUntilDeparture(departureTime) { const now = new Date(); const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE ); } 94/149
  • 67.
    Example #2 [Impl]| JavaScript function calculateMinutesUntilDeparture(departureTime) { const now = new Date(); const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - now.getTime()) / MILLISECONDS_PER_MINUTE ); } The current date/time. 95/149
  • 68.
    Option A: Injectthe current date/time from the test function calculateMinutesUntilDeparture(departureTime, currentTime) { const MILLISECONDS_PER_MINUTE = 1000 * 60; return Math.floor( (departureTime.getTime() - currentTime.getTime()) / MILLISECONDS_PER_MINUTE ); } 96/149
  • 69.
    (once again) This isdata injection. 97/149
  • 70.
    Option B: Injectan object we can stub which provides the current date/time class DepartureService { constructor(dateProvider) { this.dateProvider = dateProvider; this.MILLISECONDS_PER_MINUTE = 1000 * 60; } calculateMinutesUntilDeparture(departureTime) { const currentTime = this.dateProvider.now(); return Math.floor( (departureTime.getTime() - currentTime.getTime()) / this.MILLISECONDS_PER_MINUTE ); } } const realDateProvider = { now: () => new Date() }; 99/149
  • 71.
  • 72.
    Option C: .... Thereare likely other options that could be considered, but... 102/149
  • 73.
    I want tolearn more! See my slides about the Date Provider pattern: https://www.slideshare.net/DerekLee/standing-the- test-of-time-the-date-provider-pattern 104/149
  • 74.
  • 75.
    We're building the infrastructure fora front end mobile app to communicate with the back-end HTTP API. 106/149
  • 76.
    Remember, we'll look atthe implementation code first. 107/149
  • 77.
    ! Before we canbecome comfortable with testing, or a test-first approach, it can often be helpful to spike on and understand the implementation code first. 108/149
  • 78.
    Example #3 [Impl]| Swift iOS import Foundation struct NetworkHttp { let baseServerUrl: String func get(endpoint: String) async throws -> Data { let urlString = baseServerUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await URLSession.shared.data(from: url) return data } } 109/149
  • 79.
    Example #3 [Test]| Swift iOS import XCTest final class NetworkHttpTests: XCTestCase { func test_get_performsRequestAndReturnsData() async throws { let networkHttp = NetworkHttp(baseUrl: "https://example.com") let data = try await networkHttp.get(endpoint: "/test") XCTAssertFalse(data.isEmpty) } } 110/149
  • 80.
    What are someof the challenges with this testing approach? 111/149
  • 81.
    This test dependson real-world conditions. If the server is down or the endpoint is invalid, this test will fail. 112/149
  • 82.
    It's not deterministic. Youcan't control the response. You're at the mercy of whatever is at that URL. 113/149
  • 83.
    You can't simulateerrors. What if you wanted to test how your code handles a timeout or bad response? 114/149
  • 84.
    Flaky! Fail on badnetwork 115/149
  • 85.
  • 86.
  • 87.
    Back to theimplementation code... 118/149
  • 88.
    Example #3 [Impl]| Swift iOS import Foundation struct NetworkHttp { let baseServerUrl: String func get(endpoint: String) async throws -> Data { let urlString = baseServerUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await URLSession.shared.data(from: url) return data } } 119/149
  • 89.
    What's a deceptively simplequestion we could ask here? 120/149
  • 90.
    "What is thiscode dependent on?" 122/149
  • 91.
    "What is thiscode dependent on?" 123/149
  • 92.
    Example #3 [Impl]| Swift iOS import Foundation struct NetworkHttp { let baseServerUrl: String func get(endpoint: String) async throws -> Data { let urlString = baseServerUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await URLSession.shared.data(from: url) return data } } "What is this code dependent on?" 124/149
  • 93.
    ! There's a hintat the top of the code... anytime you see import it's a clue that there's a dependency. This was more obvious in Obj-C because imports were more detailed and not at the module level, but still... this is still a good hint. 125/149
  • 94.
    Example #3 [Impl]| Swift iOS import Foundation struct NetworkHttp { let baseServerUrl: String func get(endpoint: String) async throws -> Data { let urlString = baseServerUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await URLSession.shared.data(from: url) return data } } "What is this code dependent on?" 126/149
  • 95.
    Example #3 [Impl]| Swift iOS import Foundation struct NetworkHttp { let baseServerUrl: String func get(endpoint: String) async throws -> Data { let urlString = baseServerUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await URLSession.shared.data(from: url) return data } } "What is this code dependent on?" 127/149
  • 96.
    So how dowe make this code more testable? 128/149
  • 97.
    There's a lotto this so I'll give you the overview and you can check the details later. 129/149
  • 98.
    Example #3 [Implv2] | Swift iOS import Foundation protocol NetworkSession { func data(from url: URL) async throws -> (Data, URLResponse) } 130/149
  • 99.
    Example #3 [Testv2] | Swift iOS import XCTest final class NetworkHttpTests: XCTestCase { func test_get_returnsStubbedData() async throws { let expectedData = "Hello, world!".data(using: .utf8)! let stubHttpUrlResponse = HTTPURLResponse( url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: nil, headerFields: nil )! let stubSession = StubNetworkSession(data: expectedData, response: stubHttpUrlResponse) let networkHttp = NetworkHttp(baseUrl: "https://example.com", session: stubSession) let resultData = try await networkHttp.get(endpoint: "/test") XCTAssertEqual(resultData, "Hello, world!".data(using: .utf8)!) } } 131/149
  • 100.
    Example #3 [Stub]| Swift iOS final class StubNetworkSession: NetworkSession { let stubbedData: Data let stubbedResponse: URLResponse init(data: Data, response: URLResponse) { self.stubbedData = data self.stubbedResponse = response } func data(from url: URL) async throws -> (Data, URLResponse) { return (stubbedData, stubbedResponse) } } 132/149
  • 101.
    Example #3 [Implv2] | Swift iOS extension URLSession: NetworkSession {} 133/149
  • 102.
    Example #3 [Implv2] | Swift iOS struct NetworkHttp { let baseUrl: String let session: NetworkSession func get(endpoint: String) async throws -> Data { let urlString = baseUrl + endpoint let url = URL(string: urlString)! let (data, _) = try await session.data(from: url) return data } } 134/149
  • 103.
    Now our testis... 135/149
  • 104.
  • 105.
  • 106.
  • 107.
    Fast Deterministic Doesn't touch thenetwork Focused on our own logic 139/149
  • 108.
  • 109.
  • 110.
  • 111.
    ! Goals For ThisTalk → Help you see dependencies more easily → Sharpen your design and testing instincts → Create an “aha!” moment with one clear, repeatable, memorable question: "What is this code dependent on?" 144/149
  • 112.
    ! Anti-Goals For ThisTalk → Teach TDD directly → Prescribe a testing order → and lots, lots more... (... though I have strong, yet loosely-held, opinions on these! ) 146/149
  • 113.
    How did Ido? https://forms.gle/X18Wmspm5y2TyCfA7 Please share your feedback! 147/149
  • 114.
    Thank you! Tokyo SoftwareTesting Meetup! Attendees, Organizers, Presenters, Le Wagon! 148/149
  • 115.
    ! Thank you! Find meonline: @theextremeprogrammer https://artandscienceofcoding.com/ 149/149