111
TDD++: TDD Made Easier 2016-10-04 OX:AGILE Meetup @Elsevier Christian Hujer, Software Craftsman (Nelkinda Software Craft, Equal

2016 10-04: tdd++: tdd made easier

Embed Size (px)

Citation preview

TDD++: TDD Made Easier2016-10-04 OX:AGILE Meetup @ElsevierChristian Hujer, Software Craftsman(Nelkinda Software Craft, Equal Experts)

TopicsDefinitions of Unit TestingDefinitions of (A)TDDHow (A)TDD relates to XP, Agile, Software CraftsmanshipHow (A)TDD relates to software architectureThe Three Laws of TDDUnit Test vs Acceptance Test / White Box vs Black Box / Bottom-Up vs Top-Down: When to use which approachThe 4 As Arrange, Act, Assert, AnnihilateBDDThe Anatomy of xUnit Frameworks (JUnit, AceUnit)Cucumber, GherkinTPP

What makes TDD difficult?

When did you learn programming?When did you learn about TDD?How many years of difference?

Is Test-Development the same as Production-Development? What is different?Ask yourself

TDD can be hard because writing tests is somewhat different from writing production code. we have much less experience in writing tests than writing production code. we dont know rules, laws, guidelines, principles. we might follow bad practices.

Some Basics

The Influence of Test-Driven Development on Software Architecture

Number one benefit of TDD:It supports courage!REMOVE FEAR!

(From Robert C. Martin)The Two Valuesof Software

The secondary value of Software is its behavior: That it does what the current users currently need without any bugs, crashes or delays. Robert C. Martin

But: Users and their needs change with time!Secondary Value of Software

Primary Value of SoftwareThe ability of software to tolerate and facilitate such ongoing change is the primary value of software. The primary value of software is that its soft. Robert C. Martin

RefactoringRefactoring, n:A change to the structure of source code without significantly changing its behavior.

But what defines what change in behavior is significant?

RigidityFragilityImmobilityViscosity

What causes them?The Four Design Smells

Architecture in 3 WordsCoupling and Cohesion

(Surprising?) SynonymsTestableReusableIndependently DevelopableIndependently DeployableDecoupledAre synonyms!

Testing requires and encourages good architecture!

Test?

Whats a Test?A Test is an Executable Specification of Relevant Behavior.A Test operates by Observation of Behavior.

Unit Tests are tests that run fast. Michael Feathers,Working Effectively with Legacy CodeWhat are Unit Tests?

Acceptance TestTests on the level of a systems users.

Ideally they match acceptance criteria of user stories.

Pro Tip: Write Acceptance Criteria in Gherkin!

White / Black / Grey BoxGood Unit Tests are Grey Box Tests.(Or Black Box Tests on Class Level.)Full White Box results in tight coupling between Test and Implementation.

Do not couple your unit tests to your implementation or environment.Clean Code, SOLID and package principles, DRY also apply to tests.Do not test private methods.(Unit) Tests are System Components

Low Code Coverage Low Requirements Coverage Low Code Coverage is a strong metric.

High Code Coverage High Requirements Coverage High Code Coverage is a weak metric!

Execution is not sufficient.Verification is essential!Coverage

How to find that your test is uselessSabotage your production code to find useless tests.

If replacing a complex implementation with return "";still passes, youve mocked yourself away!

Regression TestRegression Test is NOT another type of test!

Regression Test is the repeated execution of tests in order to prevent regression.

But: Regression Tests can be those tests found to not change for a new feature.

Test Pyramid

Unit TestsAcceptance TestsUI Tests

FastAutomatedIndependentRerunnableFAIR mnemonic acronym

FastIsolatedRepeatableSelf-VerifyingTimelyFIRST mnemonic acronym

JUnit Intro

Kent Beck (SUnit) & Erich Gamma (Java)Standard for future frameworksxUnit (examples: NUnit, TestNG, CUnit, CppUnit, EmbUnit, AceUnit)JUnit History

What JUnit ProvidesTest Runner FrameworkTest Logging / Reporting FrameworkDefault Test RunnerDefault Test LoggersAutomated Test Discovery (!)Assertion Framework

JUnit Example 1 import org.junit.Test; 2 import static org.junit.Assert.*; 3 4 public class StringsTest { 5 @Test 6 public void nullHasLengthZero() { 7 assertEquals(0, Strings.length(null)); 8 } 9 }

Essential JUnit Annotations@BeforeClass@Before@Test@After@AfterClass@Ignore

JUnit Execution Sequence 1 public class MyTest { 2 @BeforeClass 3 public static void beforeClass() {} 4 @AfterClass 5 public static void afterClass() {} 6 @Before 7 public void before() {} 8 @After 9 public void after() {}10 @Test11 public void test1() {}12 @Test13 public void test2() {}14 }beforeClass()new MyTest()before()test1()after()new MyTest()before()test2()after()afterClass()

Original:A class should have should have only one reason to change. Robert C. Martin

New:On its level of abstraction, a software entity should have only one reason to change.SRP Revisited

Single Assert RuleEvery Test Case should contain only one logical assert.

Multiple assert statements are possible if they are one assertion on a logical level.

SRP - Single Responsibility Principle applies here as well.

The 4 AsA test case consists of 4 Steps:ArrangeActAssertAnnihilate

Bugfixing?Fixing Bugs without Test-First risks regression!

Therefore:Write Test to Reproduce Bug.Fix Bug.

Dos and Donts

Do Not compare boolean valuesBadassertEquals(false, v);assertEquals(true, v);assertTrue(v == true);assertFalse(v == true);GoodassertTrue(v);assertFalse(v);

Do separate As with blank lines 1 @Test 2 public void blinker() { 3 Universe horizontalBlinker = parse("...\nOOO"); 4 Universe verticalBlinker = parse(".O\n.O\n.O"); 5 6 Universe actual = horizontalBlinker.step(); 7 8 assertEquals(verticalBlinker, actual); 9 }

Do not log test steps 1 @Test 2 public void someTest() { 3 LOG.debug("Starting someTest()."); 4 // 5 LOG.debug("End of someTest()."); 6 } Pointless duplication of test runner functionality. Silence is Golden (ancient UNIX wisdom). Only make noise when crashing, but then make a lot! If its so long that it has steps, it violates the SRP.

Do Make Given When Then Explicit 1 @Test 2 public void blinker() { 3 // Given 4 Universe horizontalBlinker = parse("...\nOOO"); 5 Universe verticalBlinker = parse(".O\n.O\n.O"); 6 7 // When 8 Universe actual = horizontalBlinker.step(); 9 10 // Then11 assertEquals(verticalBlinker, actual);12 }

Disabling TestsDont comment out 1 // Takes too long 2 // @Test 3 public void someTest() { 4 // ... 5 }Use @Ignore 1 @Ignore("Takes too long") 2 @Test 3 public void someTest() { 4 // ... 5 }Disabled Tests are an Abomination!(Almost like commented-out code.)

Dont force OO without reason 1 class Strings { 2 int length(String s) { 3 return s != null 4 ? s.length() 5 : 0; 6 } 7 } 1 class Strings { 2 static int length(String s) { 3 return s != null 4 ? s.length() 5 : 0; 6 } 7 }

Dont force OO without reason 1 class StringsTest { 2 @Test 3 void nullIsZero() { 4 Strings s; 5 s = new Strings(); 6 assertEquals(0, 7 s.length(null)); 8 } 9 } 1 class StringsTest { 2 @Test 3 void nullIsZero() { 4 assertEquals(0, 5 Strings.length(null)); 6 } 7 }

Unit Tests are System ComponentsDo not test private methods.Do not couple your unit tests to your implementation.

Test Smells

Complicated Setup / FixtureRigid TestsFragile TestsTest Code DuplicationErratic TestsObscure TestsSlow TestsUnit Tests depending on external resourcesTest Smells

TDD Test-Driven Development

TDD Test-Driven DevelopmentDeveloping Software by following the Three Laws of Test-Driven Development.

ATDD Acceptance-TDDApplying / extending the principles of TDD to Acceptance Testing.

The 3 Laws of TDDYou are not allowed to write any production code unless it is to make a failing unit test pass.You are not allowed to write any more of a unit test than is sufficient to fail; and not compiling is failing.You are not allowed to write any more production code than is sufficient to pass the one failing unit test. Robert C. Martin

The Red-Green-Refactor Cycle

Red: Write as much of test as is sufficient to fail.

Green: Write as much of production code as is sufficient to pass.

Refactor: Well, refactor!

TDD + Pair Programming: Ping PongDev 1 Drives Red: Test CaseDev 2 Drives Green: Production CodeDev 2 Drives Blue: RefactorDev 2 Drives Red: Test CaseDev 1 Drives Green: Production CodeDev 1 Drives Blue: Refactor Repeat

The Importance of Tests

Much less DebuggingCourage (required for Refactoring mercilessly)Documentation (executable Specs)DesignProfessionalism Robert C. MartinAlso: Trades Fragility into Rigidity!Benefits of TDD

Importance of Test Code

You have 2 drives:Production CodeTest CodeSomehow Backups are unavailable.Which hard drive do you wish got lost?

Importance of Test CodeTreat Test Code with at least as much care and principles as the production code.That is, Clean Code, SOLID, DRY, Law of Demeter etc. all apply!

Tests & RefactoringWhat is Refactoring?What is a Test?How are they connected?

The Future Paths of CodeProject 1:Excellent DesignNo TestsProject 2:Poor DesignLots of TestsWhat will be the future of these projects?

TDD and Functional ProgrammingFunctional Programming: Programming without variable (re-)assignments. Immutable Objects Pure Functions

Immutable Objects and Pure Functions are easier to test!

Test Doubles(aka Mocks)

Replace test objectsDecouplingSpeed upProvide informationProvide verificationTest Doubles (aka Mocks)

Ontology of Test DoublesTestDoubleDummyStubSpyMockFakeOriginalreplaces

Original has any of these characteristics:Non-deterministic results (time, temperature)States are difficult to reproduceSlowDoes not (yet) existImpedes testing Because I can is not on the list! To isolate the test class is not on the list!Reasons for Using Test Doubles

Mocking candeteriorate test focus,violate the Law of Demeter,lead to fragile tests,lead to worthless tests,be harmful! So use mocking judiciously!Conclusions from Case Study

Rigorously mock all external dependencies!Use mocking judiciously!Mocking might make you lose test focus.If the real code couldnt pass the test in place of the mock, the mock is wrong!Guidelines for Mocking in Unit Tests

Guidelines for Dependency InjectionUse Dependency Injection judiciously!Inject dependencies for Strategies and Singletons, but not just because you can!Theres nothing wrong with good old concepts like static methods when they perfectly well do the job!

OtherClasses can depend on functionality of other classes in two ways:Loosely coupled, as in Aggregation Mocks are usefulTightly coupled, as in Composition Mocks are harmful

BDD Behavior-Driven Development

BDD Behavior-Driven DevelopmentExtension of TDD that makes use of a simple, domain-specific language (usually around the words given, when, then).

BDD - Behavior Driven DevelopmentArrange: GivenAct: WhenAssert: Then(Annihilate: Hidden nicely in the background)

4 As BDDArrangeActAssert(Annihilate)GivenWhenThen

Gherkin and Cucumber

Behavior-Driven DevelopmentAcceptance Test-Driven DevelopmentWhats it about?

Given: Arrangement, PreconditionsWhen: ActionThen: Assertions, VerificationGiven-When-Then

Gherkin Example 1 Scenario: As a User, I want to start the editor 2 Given I have just started the editor 3 Then the document name must be "" 4 And the editor has focus 5 And the document must have the following content: 6 """ 7 """ 8 And the window title must be "Editor: "

Feature: Delivery Date Prediction

Scenario: Ordering on a workday before cutoff time. Given product id 1234 with cutoff time 13:00. Given the current datetime is 2016-01-02T12:59. Given DHL is my default delivery service. When viewing that product, Then the displayed delivery date is 2016-01-03.Gherkin

1 Scenario: As a User, I want to load a file 2 Given I have just started the editor 3 And the file "foo.txt" has the following content: 4 """ 5 Hello, World! 6 """ 7 When I action "open" 8 Then I must be asked for a filename 9 When I enter the filename "foo.txt"10 And I wait for I/O to be completed11 Then the document must have the following content:12 """13 Hello, World!14 """15 And the document name must be "foo.txt".

FeatureBackgroundScenarioScenario OutlineExamples|| (Data Tables)@ (Tags)Gherkin KeywordsGivenWhenThenAndBut""" (Doc Strings)# (Comments)

Feature Files: *.feature 1 Feature: Simple Calculator Usage 2 Background: 3 Given the initial state of the calculator 4 5 Scenario: Add two numbers 6 When I press "10+10=" 7 Then the result on the display is "20" 8 9 Scenario: Multiplying three numbers10 When I press "2*3*4="11 Then the result on the display is "24"

1 Feature: Calculator 2 Scenario Outline: Adding numbers 3 Given my current state is cleared, 4 When I enter the "", 5 And I press "+", 6 And I enter the "", 7 And I press "=", 8 Then the result must be "". 9 Examples:10 | first number | second number | sum |11 | 10 | 20 | 30 |12 | 20 | 5 | 25 |13 | 30 | 1 | 31 |

Scenario Outline Example

Smartness simplifies your life 1 Feature: Calculator 2 Scenario Outline: Performing Calculations 3 When I enter "" 4 Then the result must be "" 5 Examples: 6 | input | output | 7 | 1+1= | 2 | 8 | 2*3*4= | 24 | 9 | 2^2^2= | 16 |10 | 5! | 120 |

Gherkin TipsBe Concise.Use must for verifications that must not fail.Prefer Scenario Outlines over Scenarios.Reuse StepDefs as much as possible.Ignore punctuation at the end (comma, full stop).

Compare and discuss pros / cons 1 Given a property id:"pii:42" key:"sa:userLicense" with the following value exists: 2 """ 3 foo 4 """

1 Given I POST the following payload to "/properties/pii/id:42?key=sa:userLicense": 2 """ 3 foo 4 """

Cucumber Java 7

Annotation BasedCucumber Java 7

Step Definitions 1 package ; 2 3 public class EditorStepdefs { 4 @Given("^I have just started the editor$") 5 public void iHaveJustStartedTheEditor() { /* */ } 6 7 @When("^I action \"([^\"]*)\"$") 8 public void iAction(String actionCommand) { /* */ } 9 10 @Then("^the document must have the following content:$")11 public void expectDoc(String text) { /* */ }12 }

Connecting Cucumber with JUnit 1 package com.elsevier.; 2 3 import cucumber.api.junit.Cucumber; 4 import org.junit.runner.RunWith; 5 6 @Cucumber.Options( 7 features = { "src/test/resources/features/" }, 8 glue = { "com.elsevier..steps" } 9 )10 @RunWith(Cucumber.class)11 public class RunFeatures {12 }

Make StepDefs as reusable as possible.Dont group StepDefs by Features.Group StepDefs by Topics / Dependencies.

Cucumber Tips

Cucumber Java 8 1 import cucumber.api.PendingException; 2 import cucumber.api.java8.En; 3 public class MyStepdefs implements En {{ 4 Given("^product id (\\d+) with cutoff time (\\d+:\\d+)[.,]?$", (String id, String time) -> { 5 throw new cucumber.api.PendingException(); 6 }); 7 Given("^the current datetime is (\\d+-\\d+-\\d+T\\d+:\\d+)[.,]?$", (String datetime) -> { 8 throw new PendingException(); 9 });10 Given("^\"([^\"]+)\" is my default delivery service[.,]?$", (String deliveryService) -> {11 throw new PendingException();12 });13 When("^viewing that product[.,]?$", () -> {14 throw new PendingException();15 });16 Then("^the displayed delivery date is (\\d+-\\d+-\\d+)[.,]?$", (String expectedDeliveryDate) -> {17 throw new PendingException();18 });19 }}

Syntax Highlighting of Feature FilesMatching of steps and step definitionsCompletion of keywordsCompletion of stepsCreation of stub step definitions (Groovy, Java, Java 8)Cucumber Test ExecutionJUnit Test ExecutionSupport in IntelliJ IDEA

Make Gherkin SuccessfulInvolve Product Owner / Business AnalystsReusable Step Definitions This requires constant attention!

Successful Usage of Cucumberrequires a lot of discipline fromauthors of feature filesdevelopers providing step definitionsTo ensure consistency across the whole project. Cucumber is an art!

TPP Transformation Priority Premise

TPP Transformation Priority PremisePremise that production code is evolved best by writing tests which demand applying transformations in a specific sequence.

TransformationTransformation, n:A change to the behavior of source code without significantly changing its structure.

TPP - Transformation Priority PremiseAs the tests get more specific, the code gets more generic. Robert C. Martin

List of Transformations{} nilnil constantconstant constant +constant scalarstatement statementsunconditional if

scalar arrayarray containerstatement recursionif whileexpression functionvariable assignment

TPP AppliedStart: Define the most degenerate good case.Induction: Demand the simplest transformation possible.

How it worksThe first unit test should demand the first transformation.The next unit test should demand the simplest next transformation possible.Apply the simplest transformations that still satisfy the test.And: Red Green Refactor

Rants and WTFs

Integration Tests that dont Integrate?!Separate Regression Tests???(All Tests are Regression Tests!)And Mocking / Mockito is totally overused! Where are your skills for abstraction?Are we out of our mind?!

Possible Future Meetup TopicsThe SOLID PrinciplesBeyond SOLID: Package Principlesaka Vertical vs Horizontal ArchitectureJUnit 5BDD with Gherkin and Cucumber-JVMCucumber-Java8Mocking with MockitoSVG Scalable Vector Graphics

Possible Future Meetup TopicsClean Code Refactoring: Expense Report ExampleTDD: Bowling Game Example(A)TDD Swing Text Editor Example(A)TDD Web server ExampleFunctional Programming in Java 8Functional Programming (3 Parts)Unicode and Character EncodingsTransformation Priority Premise

Possible Future Meetup TopicsThe UNIX Way To The Web GNU make, XSLT and NetPBM to generate stunningly fast XHTML5How to increase the Quality of your WebsiteC and Embedded TDD with AceUnit (on Arduino and Raspberry Pi)

Possible Future Meetup TopicsSpeakers welcome!

2016-10-22 Global Day of CoderetreatMore than 150 cities worldwidehttps://www.meetup.com/Agile-Software-Development-Oxfordshire/events/234142930/

Thank you!Questions? Feedback?

https://seasidetesting.com/2013/03/12/testing-and-the-two-values-of-software/http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTddhttp://programmer.97things.oreilly.com/wiki/index.php/The_Three_Laws_of_Test-Driven_Development

References