{"id":36,"date":"2026-06-28T16:37:32","date_gmt":"2026-06-28T14:37:32","guid":{"rendered":"https:\/\/missud.eu\/index.php\/2026\/06\/28\/dojo-tdd-boite-noire-vs-boite-blanche-tester-le-comportement-ou-limplementation\/"},"modified":"2026-06-28T16:41:21","modified_gmt":"2026-06-28T14:41:21","slug":"dojo-tdd-boite-noire-vs-boite-blanche-tester-le-comportement-ou-limplementation","status":"publish","type":"post","link":"https:\/\/missud.eu\/en\/2026\/06\/28\/dojo-tdd-boite-noire-vs-boite-blanche-tester-le-comportement-ou-limplementation\/","title":{"rendered":"Dojo TDD \u2014 Black Box vs White Box: testing behavior or implementation?"},"content":{"rendered":"<p class=\"wp-block-paragraph\">A coding dojo is a deliberate training session: a well-defined problem, explicit constraints, and a thought-provoking debriefing. This one revolves around a library system and a fundamental question: <strong>When writing tests, are we testing a behavior or an implementation?<\/strong> The repositories are available on GitHub: <a href=\"https:\/\/github.com\/dmissud\/black-box\">black-box<\/a> And <a href=\"https:\/\/github.com\/dmissud\/white-box\">white-box<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The system \u2014 a library in hexagonal architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The domain is intentionally simple. Three business concepts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Member<\/strong> \u2014 a subscriber, of a certain type <code>CHILD<\/code>, <code>ADULT<\/code> Or <code>PREMIUM<\/code><\/li>\n<li><strong>Book<\/strong> \u2014 a book, categorized <code>YOUTH<\/code>, <code>STANDARD<\/code> Or <code>PREMIUM<\/code><\/li>\n<li><strong>Loan<\/strong> \u2014 an existing loan, with a start date, expiry date and optional return date<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The architecture follows the ports &amp; adapters pattern: the domain exposes interfaces (<code>MemberUseCase<\/code>, <code>BookUseCase<\/code>, <code>LoanUseCase<\/code>) and output ports (<code>MemberRepository<\/code>, <code>BookRepository<\/code>, <code>LoanRepository<\/code>The infrastructure provides in-memory H2 implementations. The domain does not depend on anything external.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Integration tests (<code>LibraryIntegrationTest<\/code>) already cover the basic functionalities: registering a member, adding a book to the catalog, borrowing, returning, listing active loans. These tests are successful at the start \u2014 this is the solid foundation on which the dojo will build.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The mission \u2014 to implement a policy for access to loans<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">THE <code>LoanService<\/code> The initial approach is naive: it borrows without any professional verification. The dojo&#039;s mission is to implement three rules in <code>LoanAccessPolicyTest<\/code> following the RED \u2192 GREEN \u2192 REFACTOR cycle:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Rule 1 \u2014 Access by category<\/strong> : YOUTH is accessible to all; STANDARD to ADULT and PREMIUM; PREMIUM to PREMIUM only.<\/li>\n<li><strong>Rule 2 \u2014 Quota of simultaneous borrowing<\/strong> : 2 for CHILD, 5 for ADULT, 10 for PREMIUM.<\/li>\n<li><strong>Rule 3 \u2014 Maximum duration<\/strong> 21 days from today, not one more.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In case of a violation, a dedicated business exception must be raised: <code>BookAccessDeniedException<\/code>, <code>LoanQuotaExceededException<\/code>, <code>LoanDurationExceededException<\/code>. Both groups receive exactly the same instructions, but with a different constraint on how to write the tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Black box \u2014 test the contract, ignore the inside<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The black box instruction is explicit: <em>\u00ab&quot;You must not read any classes from the packages&quot; <code>application<\/code> Or <code>infrastructure<\/code>. You only test via the interfaces defined in <code>domain\/port\/in<\/code>.\u00a0\u00bb<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In practice, the test setup mounts a real H2 database in memory and instantiates the services via their interfaces:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private MemberUseCase memberUseCase;\nprivate BookUseCase   bookUseCase;\nprivate LoanUseCase   loanUseCase;\n\n@BeforeEach\nvoid setUp() throws SQLException {\n    DatabaseConfig.initialize();\n    memberUseCase = new MemberService(new H2MemberRepository());\n    bookUseCase   = new BookService(new H2BookRepository());\n    loanUseCase   = new LoanService(...);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To test rule 1, we actually create a CHILD member and a STANDARD book in the database, then we attempt to borrow via <code>loanUseCase.borrow()<\/code> and we check for the exception. No mock, no stub: the entire system runs. The constraint is not technical, it is intellectual \u2014 we don&#039;t look at the service code to know what it does, we deduce the expected behavior from the specifications.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#039;s uncomfortable at first. We don&#039;t know if <code>LoanService<\/code> already implements the rule or not. We write the RED test blindly, then we run it <code>mvn test<\/code> To see. That&#039;s exactly it, TDD.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">White box \u2014 test quickly, with mocks on concrete classes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The white box instruction opens everything: <em>\u00ab&quot;You have access to all the source code: models, services, repositories. You can mock the repositories directly and instantiate them.&quot; <code>LoanService<\/code> by hand.\u00bb<\/em> She adds an educational note: <em>\u00ab&quot;Keep in mind that you will discover at the end of the session why too strong a coupling to concrete classes can become a problem.&quot;\u00bb<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The setup is minimal thanks to Mockito:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Mock private H2MemberRepository memberRepository;\n@Mock private H2BookRepository   bookRepository;\n@Mock private H2LoanRepository   loanRepository;\n\n@BeforeEach\nvoid setUp() {\n    loanService = new LoanService(loanRepository, memberRepository, bookRepository);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each test configures exactly what it needs. For the quota rule, we stubble <code>findActiveByMemberId<\/code> to return a list of N existing loans, without modifying the database:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Test @DisplayName(&quot;A child cannot have more than 2 simultaneous loans&quot;) void shouldRejectLoanWhenChildReachedMaxQuota() { List activeLoans = List.of( new Loan(UUID.randomUUID(), memberId, UUID.randomUUID(), LocalDate.now(), LocalDate.now().plusDays(7)), new Loan(UUID.randomUUID(), memberId, UUID.randomUUID(), LocalDate.now(), LocalDate.now().plusDays(7)) ); when(memberRepository.findById(memberId)) .thenReturn(Optional.of(new Member(memberId, &quot;Alice&quot;, MemberType.CHILD))); when(bookRepository.findById(bookId)) .thenReturn(Optional.of(new Book(bookId, &quot;Book&quot;, &quot;Author&quot;, BookCategory.YOUTH))); when(loanRepository.findActiveByMemberId(memberId)).thenReturn(activeLoans); assertThrows(LoanQuotaExceededException.class, () -&gt; loanService.borrow(memberId, bookId, LocalDate.now().plusDays(7)) ); }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The tests are fast, accurate, and isolated. Each test checks exactly one thing, without noise. Borderline case coverage is easily achieved.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The TDD progression \u2014 RED, GREEN, REFACTOR<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In both cases, the progression follows the same rhythm. We begin by writing a first RED test on the simplest rule\u2014a CHILD trying to borrow a STANDARD book. The test fails because the <code>LoanService<\/code> The starting point doesn&#039;t verify anything. That&#039;s intentional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We implement the minimum in <code>LoanService.borrow()<\/code> To perform this test: retrieve the member, retrieve the book, compare the types, and raise the exception if necessary. The test passes (GREEN). No further action is required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then we add the next test\u2014an ADULT trying to purchase a PREMIUM book\u2014and extend the rule. Then the quota rule. Then the duration. With each iteration, the existing tests act as a safety net: you can&#039;t break a rule that&#039;s already been implemented without it being immediately apparent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The REFACTOR phase occurs naturally when conditions accumulate in <code>borrow()<\/code>. We can extract a method <code>checkAccessPolicy()<\/code>, or a dedicated class. The tests remain green: they are what define what the refactoring must preserve.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The debrief \u2014 why it really matters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is where the dojo becomes truly valuable. We compare the two test bases in the face of a refactoring scenario: we rename <code>H2MemberRepository<\/code> in <code>InMemoryMemberRepository<\/code>, or the access policy is extracted into a dedicated domain object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">White-box tests fail immediately. They mocked <code>H2MemberRepository<\/code> by its class name \u2014 the compiler complains before even running the tests. Worse: if we refactor <code>LoanService.borrow()<\/code> to delegate the verification to a <code>Loan Policy<\/code>, The mocks no longer correspond to the new structure. Tests that previously tested the same business rule need to be rewritten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Black box tests remain unchanged. They know neither <code>H2MemberRepository<\/code> neither <code>LoanService<\/code>. They just know that <code>loanUseCase.borrow(childId, standardBookId, dueDate)<\/code> must raise an exception. This contract is stable \u2014 it is defined by the business rule, not by the current implementation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pedagogical value of the white box instruction took on its full meaning at that moment: <em>\u00ab&quot;Too strong a coupling with concrete classes can become a problem.&quot;\u00bb<\/em> \u2014 it wasn&#039;t an abstract warning, it was a prophecy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">My takeaway from this<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The white box allows you to write tests faster, with more control over edge cases. It&#039;s often more comfortable, especially when starting out with TDD. But this comfort comes at a price: tests adhere to the internal structure, and every refactoring becomes a risk of unnecessary breakage.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The black box approach requires an initial effort\u2014understanding and formulating the expected behavior without looking at the implementation. But it produces tests that last, document business rules, and unleash refactoring rather than hinder it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The source code is on GitHub: <a href=\"https:\/\/github.com\/dmissud\/black-box\">dmissud\/black-box<\/a> And <a href=\"https:\/\/github.com\/dmissud\/white-box\">dmissud\/white-box<\/a>. The instructions are in <code>INSTRUCTIONS.md<\/code> at the root of each repo \u2014 sufficient to animate the session with any Java group.<\/p>","protected":false},"excerpt":{"rendered":"<p>Un dojo de code, c&rsquo;est une s\u00e9ance d&rsquo;entra\u00eenement d\u00e9lib\u00e9r\u00e9 : un probl\u00e8me bien d\u00e9fini, des contraintes explicites, et un d\u00e9brief qui fait r\u00e9fl\u00e9chir. Celui-ci tourne autour d&rsquo;un syst\u00e8me de biblioth\u00e8que et d&rsquo;une question fondamentale : quand on \u00e9crit des tests, est-ce qu&rsquo;on teste un comportement ou une impl\u00e9mentation ? Les repos sont disponibles sur GitHub [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-36","post","type-post","status-publish","format-standard","hentry","category-craftsmanship"],"_links":{"self":[{"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/posts\/36","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/comments?post=36"}],"version-history":[{"count":1,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/posts\/36\/revisions"}],"predecessor-version":[{"id":37,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/posts\/36\/revisions\/37"}],"wp:attachment":[{"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/media?parent=36"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/categories?post=36"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/missud.eu\/en\/wp-json\/wp\/v2\/tags?post=36"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}