Slide 1

Slide 1 text

@yot88 Clean tests

Slide 2

Slide 2 text

Mythbusters In small groups, categorize each sentence about Unit tests in : • Myth • Truth Sentences • It makes changes more difficult to make • Using them, I don't have a piece of code that I'm afraid to touch • Unit tests are only written by testers • My code is so simple, so I do not need to write a single test on it • Unit tested code is of higher quality than not tested code • You only need unit testing when there are many developers • It reduces the cost of development • It takes too much time to write

Slide 3

Slide 3 text

Mythbusters • Makes changes easier to make • Let developers refactor without fear (again, again, and again) It makes changes more difficult to make

Slide 4

Slide 4 text

Mythbusters When you refactor / add new features it acts as a safety net and increase your confidence Using them, I don't have a piece of code that I'm afraid to touch

Slide 5

Slide 5 text

Mythbusters • Usually, they don’t… • Developers write unit tests • Ideally run them every time they make any change on the system Unit tests are only written by testers

Slide 6

Slide 6 text

Mythbusters Simple code requires simple tests, so there are no excuses. My code is so simple, I do not need to write a single test on it

Slide 7

Slide 7 text

Mythbusters • It identifies every defect that may have come up before code is sent further for integration testing • Writing tests makes you think harder about the problem • It exposes the edge cases and makes you write better code Unit tested code is of higher quality than not tested one

Slide 8

Slide 8 text

Mythbusters • Unit testing can help a one-person team just as much as a 50-person team • Even more risky to let a single person hold all the cards You only need unit testing when there are many developers

Slide 9

Slide 9 text

Mythbusters • Since the bugs are found early, unit testing helps reduce the cost of bug fixes • Bugs detected earlier are easier to fix It reduces the cost of development

Slide 10

Slide 10 text

Mythbusters • It takes a little while to get used to, but overall will save you time and cut down on wasted time • Regression testing will keep things moving forward without having to worry too much How do you test your development if you do not write Unit test ? Our responsibility to reduce the cost of quality It takes too much time to write

Slide 11

Slide 11 text

Testing Principles, Practices and Patterns

Slide 12

Slide 12 text

Goal of Unit Testing Enable sustainable growth of the software project Project without tests Quickly slows down to the point that it’s hard to make any progress Software Entropy Fight entropy • Constant cleaning and refactoring • Tests act as a safety net A tool that provides insurance against a vast majority of regressions

Slide 13

Slide 13 text

Good vs Bad Tests Badly written tests result in the same picture Not all tests are created equal • Bad tests : raise false alarms • Unit tests are also vulnerable to bugs and require maintenance Tests are code too View them as part of your code base that aims at solving a particular problem: ensuring the application’s correctness

Slide 14

Slide 14 text

What makes a successful test suite? • Integrated into the development cycle Ideally : execute them on every change • Targets the most important parts of your code base Domain model – contains the business logic Testing this gives the best ROI • Provides maximum value with minimum maintenance costs Recognizing a valuable test (and, by extension, a test of low value) Writing a valuable test

Slide 15

Slide 15 text

What is a unit test?

Slide 16

Slide 16 text

What is a unit test? A unit test is an automated test that : • Verifies a small piece of code (also known as a unit) • Does it quickly • And does it in an isolated manner.

Slide 17

Slide 17 text

How would you test this ? https://github.com/ythirion/clean-tests public class CustomerService { public static Try purchase( Store store, ProductType product, Integer quantity ) { return (!store.hasEnoughInventory(product, quantity)) ? Failure(new IllegalArgumentException("Not enough inventory")) : Success(store.removeInventory(product, quantity)); } }

Slide 18

Slide 18 text

2 schools London school Classical / Detroit school A unit is A class Uses test doubles for A class or a set of classes All but immutable dependencies Shared dependencies

Slide 19

Slide 19 text

Test example London school Classical / Detroit school A unit is A class Uses test doubles for A class or a set of classes / behavior All but immutable dependencies Shared dependencies class ClassicalCustomerTests { private final Store store = new Store(HashMap.empty()) .addInventory(ProductType.Book, 10); @Test void it_should_purchase_successfully_when_enough_inventory() { final var updatedStore = CustomerService.purchase(store, ProductType.Book, 6); assertThat(updatedStore.isSuccess()).isTrue(); assertThat(updatedStore.get().getInventoryFor(ProductType.Book)).isEqualTo(4); } @Test void it_should_fail_when_not_enough_inventory() { final var updatedStore = CustomerService.purchase(store, ProductType.Book, 11); assertThat(updatedStore.isFailure()).isTrue(); assertThat(updatedStore.getCause()) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Not enough inventory"); } } @ExtendWith(MockitoExtension.class) class LondonCustomerTests { private final int QUANTITY = 6; @Mock private Store storeMock; @Test void it_should_purchase_successfully_when_enough_inventory() { when(storeMock.hasEnoughInventory(ProductType.Book, QUANTITY)).thenReturn(true); final var updatedStore = CustomerService.purchase(storeMock, ProductType.Book, 6); assertThat(updatedStore.isSuccess()).isTrue(); verify(storeMock, times(1)).removeInventory(ProductType.Book, QUANTITY); } @Test void it_should_fail_when_not_enough_inventory() { final var updatedStore = CustomerService.purchase(storeMock, ProductType.Book, 11); assertThat(updatedStore.isFailure()).isTrue(); assertThat(updatedStore.getCause()) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Not enough inventory"); verify(storeMock, never()).removeInventory(ProductType.Book, QUANTITY); } }

Slide 20

Slide 20 text

3 Pillars / 1 foundation • Protection against regressions A regression = a software bug The larger the code base, the more exposure it has to potential bugs Tests should reveal those regressions • Resistance to refactoring The degree to which a test can sustain a refactoring of the underlying application code without turning red (failing) • Fast feedback The faster the tests, the more of them you can have in the suite and the more often you can run them -> shorten the feedback loop Maintainability (Maintenance costs) How hard it is to understand the test How hard it is to run the test Good Unit Tests Protection Resistance Fast feedback Maintainability

Slide 21

Slide 21 text

Properties of Good Unit Tests – F.I.R.S.T Fast Tests should be fast enough that you won't be discouraged from using them Isolates Tests should not depend on the state of another test Repeatable Tests should be repeatable in any environment without varying results Self validating Each test will have a single boolean output of pass or fail Thorough The tests we write should • cover all happy paths • cover edge/corner/boundary cases

Slide 22

Slide 22 text

class CalculatorTests { @Test void calculator_should_sum_2_numbers() { // Arrange | Given final var first = 10; final var second = 20; // Act | When final var result = Calculator.sum(first, second); // Arrange | Then assertThat(result).isEqualTo(30); } } Anatomy of a Unit Test Class-container for a cohesive set of tests Arrange / Given : bring the system under test (SUT) and its dependencies to a desired state Act / When : Invoke the behavior • call method / function on the SUT • pass the prepared dependencies Assert / Then : verify the outcome 3 ways to do it : • the return value • the final state of the SUT and its collaborators • or the methods the SUT called on those collaborators Name of the test Junit Glue import org.junit.jupiter.api.Test;

Slide 23

Slide 23 text

Naming Unit Test It’s important to give expressive names to your tests Proper naming helps you understand what the test verifies and how the underlying system behaves. “delivery is invalid” “delivery should be invalid” “delivery with past date should be invalid” Why ? Must contain Scenario Function Under Test ExpectedResult Why ?

Slide 24

Slide 24 text

Naming Unit Test • Don’t follow a rigid naming policy You simply can’t fit a high-level description of a complex behavior into the narrow box of such a policy Allow freedom of expression • Name the test as if you were describing the scenario to a non-programmer who is familiar with the problem domain A domain expert or a business analyst should understand it • Use sentences Doing so helps improve readability, especially in long names

Slide 25

Slide 25 text

Test doubles Test double : term that describes all kinds of non-production-ready, fake dependencies in tests • spies : written manually • mocks : created with the help of a mocking framework Difference is in how intelligent they are • dummy : simple, hardcoded value • stub : fully fledged dependency that you configure to return different values for different scenarios • fake : a fake is the same as a stub for most purposes. • Usually implemented to replace a dependency that doesn’t yet exist

Slide 26

Slide 26 text

Stub / Mock Help to emulate and examine outcoming interactions Help to emulate incoming interactions

Slide 27

Slide 27 text

Stub / Mock in mockito Help to emulate and examine outcoming interactions Help to emulate incoming interactions when(storeMock.hasEnoughInventory(ProductType.Book, QUANTITY)).thenReturn(true); when() / then() Or given() in BDDMockito verify() verify(storeMock, times(1)).removeInventory(ProductType.Book, QUANTITY);

Slide 28

Slide 28 text

3 styles of Unit Tests State-based Communication-based • Feed an input to the system under test (SUT) • Check the output it produces • Tests substitute the SUT’s collaborators with mocks • Verify that the SUT calls those collaborators correctly Output-based Assumes there are no side effects and the only result of the SUT’s work is the value it returns to the caller Also known as functional (side-effect-free code) • Verify the state of the system after an operation is complete • “State” can refer to the state of • The SUT itself • One of its collaborators • Or an out-of-process dependency - the database or the filesystem Verify the final state of the system after an operation is complete

Slide 29

Slide 29 text

3 styles of Unit Tests - examples State-based Communication-based Output-based Product("Kaamelott") Product(”Free Guy") @Test void discount_of_2_products_should_be_2_percent() { val product1 = new Product("Kaamelott"); val product2 = new Product("Free Guy"); // Call on the SUT (here PriceEngine) // No side effects -> Pure function val discount = PriceEngine.calculateDiscount(product1, product2); assertThat(discount).isEqualTo(0.02); } @Test void greet_a_user_should_send_an_email_to_it() { final var email = "[email protected]"; final var emailGatewayMock = mock(EmailGateway.class); // Substitute collaborators with Test Double final var sut = new Controller(emailGatewayMock); sut.greetUser(email); // Verify that the SUT calls those collaborators correctly verify(emailGatewayMock, times(1)).sendGreetingsEmail(email); } @Test void it_should_add_given_product_to_the_order() { val product = new Product("Free Guy"); val sut = new Order(); sut.add(product); // Verify the state assertThat(sut.getProducts()) .hasSize(1) .allMatch(item -> item.equals(product)); }

Slide 30

Slide 30 text

3 styles compared Refactorings are harder with this approach If you change the interaction you need to change the tests as well

Slide 31

Slide 31 text

4 types of code All production code can be categorized along two dimensions: • Complexity or domain significance • The number of collaborators • Code complexity : defined by the number of decision- making (cyclomatic complexity for example) • Domain significance : how significant the code is for the problem domain of your project Number of collaborators : number of collaborators a class or a method has code with many collaborators is expensive to test

Slide 32

Slide 32 text

4 types of code • Domain model and algorithms : Complex code is often part of the domain model • Trivial code : Parameter less constructors / one-line properties: they have few (if any) collaborators and exhibit little complexity or domain significance • Controllers : This code doesn’t do complex or business critical work by itself but coordinates the work of other components like domain classes and external applications • Overcomplicated code : Such code scores highly on both metrics: it has a lot of collaborators, and it’s also complex or important. • Ex : fat controllers that don’t delegate complex work anywhere and do everything themselves Unit testing this gives the best return for your efforts Refactor overcomplicated code by splitting it into algorithms and controllers

Slide 33

Slide 33 text

Unit vs Integration tests

Slide 34

Slide 34 text

Integration tests

Slide 35

Slide 35 text

Let’s practice – Anti-Patterns Open the clean-tests project : • Identify anti-patterns / code smells in the 3 test classes in the anti-patterns package – 10’ • Collective debriefing – 10’ • Let’s refactor – 15’ • Collective debriefing – 10’ https://github.com/ythirion/clean-tests

Slide 36

Slide 36 text

@UtilityClass public class PriceEngine { public static Double calculateDiscount(Product... products) { val discount = products.length * 0.01; return min(discount, 0.2); } } class PriceEngineTests { @Test void discount_of_2_products_should_be_2_percent() { val products = List.of(new Product("P1") , new Product("P2"), new Product("P3")); val discount = PriceEngine.calculateDiscount(products.toArray(new Product[0])); assertThat(discount).isEqualTo(products.size() * 0.01); } } Anti-Patterns : Leaking algorithm implementation in tests Production code

Slide 37

Slide 37 text

Anti-Patterns : Leaking algorithm implementation in tests Fine to use "hardcoded" values in test It is the expected result from our test case class PriceEngineRefactoredTests { @Test void discount_of_2_products_should_be_3_percent() { val products = List.of(new Product("P1"), new Product("P2"), new Product("P3")); val discount = PriceEngine.calculateDiscount(products.toArray(new Product[0])); assertThat(discount).isEqualTo(0.03); } }

Slide 38

Slide 38 text

class TodoTests { @Test void it_should_call_search_on_repository_with_the_given_text() { val todoRepositoryMock = mock(TodoRepository.class); val todoService = new TodoService(todoRepositoryMock); val searchResults = List.of( new Todo("Create miro", "add code samples in the board"), new Todo("Add myths in miro", "add mythbusters from ppt in the board") ); val searchedText = "miro"; when(todoRepositoryMock.search(searchedText)) .thenReturn(searchResults); val result = todoRepositoryMock.search(searchedText); assertThat(result).isEqualTo(searchResults); verify(todoRepositoryMock, times(1)).search(searchedText); } } Anti-Patterns : Test external libs in our tests The SUT is a mock… • Assert that the call on our mock returns what we setup • We test Mockito here...

Slide 39

Slide 39 text

class TodoRefactoredTests { @Test void it_should_call_search_on_repository_with_the_given_text() { val todoRepositoryMock = mock(TodoRepository.class); val todoService = new TodoService(todoRepositoryMock); val searchResults = List.of( new Todo("Create miro", "add code samples in the board"), new Todo("Add myths in miro", "add mythbusters from ppt in the board") ); val searchedText = "miro"; when(todoRepositoryMock.search(searchedText)) .thenReturn(searchResults); val result = todoService.search(searchedText); assertThat(result).isEqualTo(searchResults); verify(todoRepositoryMock, times(1)).search(searchedText); } } Anti-Patterns : Test external libs in our tests Never use a Mock as SUT If not familiar : use AAA in comments can help Call the true SUT : • In this simple version will only delegates call to Repository • Would have more responsibility in real life (authorization, quotas, filtering, ...)

Slide 40

Slide 40 text

@Test void it_should_return_a_Right_for_valid_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); assertThat(result.isRight()).isTrue(); } @Test void it_should_add_a_comment_with_the_given_text() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val text = "Amazing article !!!"; val result = article.addComment(text, "Pablo Escobar"); assertThat(result.get().getComments()) .hasSize(1) .anyMatch(comment -> comment.getText().equals(text)); } @Test void it_should_add_a_comment_with_the_given_author() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val author = "Pablo Escobar"; val result = article.addComment("Amazing article !!!", author); assertThat(result.get().getComments()) .hasSize(1) .anyMatch(comment -> comment.getAuthor().equals(author)); } @Test void it_should_add_a_comment_with_the_date_of_the_day() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); } Anti-Patterns : Only 1 assert / test 4 tests to maintain but here we check a single behavior : Add a comment in an article Tests should be behavior oriented not data oriented

Slide 41

Slide 41 text

@Test void it_should_return_a_Right_for_valid_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); assertThat(result.isRight()).isTrue(); } Anti-Patterns : Technical concepts in tests name @Test void it_should_return_a_Left_when_adding_existing_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar") .map(a -> a.addComment("Amazing article !!!", "Pablo Escobar")) .flatMap(r -> r); assertThat(result.isLeft()).isTrue(); } What is a Left ? What is a Right ?

Slide 42

Slide 42 text

@Test void it_should_return_a_Right_for_valid_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); assertThat(result.isRight()).isTrue(); } Anti-Patterns : ambiguity What is a valid comment ?

Slide 43

Slide 43 text

Anti-Patterns : Missing assertions Tests without assertions do not provide any value Seeing a test failing is as important as seeing it passing @Test void it_should_add_a_comment_with_the_date_of_the_day() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); }

Slide 44

Slide 44 text

@Test void it_should_add_a_comment_with_the_date_of_the_day() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); } @Test void it_should_return_a_Left_when_adding_existing_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar") .map(a -> a.addComment("Amazing article !!!", "Pablo Escobar")) .flatMap(r -> r); assertThat(result.isLeft()).isTrue(); } class BlogTests { @Test void it_should_return_a_Right_for_valid_comment() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val result = article.addComment("Amazing article !!!", "Pablo Escobar"); assertThat(result.isRight()).isTrue(); } @Test void it_should_add_a_comment_with_the_given_text() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val text = "Amazing article !!!"; val result = article.addComment(text, "Pablo Escobar"); assertThat(result.get().getComments()) .hasSize(1) .anyMatch(comment -> comment.getText().equals(text)); } @Test void it_should_add_a_comment_with_the_given_author() { val article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); val author = "Pablo Escobar"; val result = article.addComment("Amazing article !!!", author); assertThat(result.get().getComments()) .hasSize(1) .anyMatch(comment -> comment.getAuthor().equals(author)); } Anti-Patterns : Duplication everywhere Tests are code too proceed with the same care

Slide 45

Slide 45 text

Anti-Patterns : Refactored BlogTests Removed duplication • If you need to instantiate a lot of object, centralize it in TestDataBuilders • If you change your models, it will be easier to maintain Step by step guide Check 1 behavior per test • Multiple assertions in test • Easier to read / understand / maintain • Help identify missing test cases : "add a new comment in an Article containing existing comments" Be careful with date • Non-deterministic data • Ideally handle it by passing a function / clock retrieving Date and Time Rename test to be more business oriented Avoid generic names like result, context, … class Blog_should { private final String text = "Amazing article !!!"; private final String author = "Pablo Escobar"; private Article article; private static void assertComment(Comment comment, String expectedText, String expectedAuthor) { assertThat(comment.getText()).isEqualTo(expectedText); assertThat(comment.getAuthor()).isEqualTo(expectedAuthor); assertThat(comment.getCreationDate()).isBeforeOrEqualTo(LocalDate.now()); } @BeforeEach void init() { article = new Article( "Lorem Ipsum", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" ); } @Nested class add_a_new_comment { @Test void in_the_article_including_given_text_and_author() { val updatedArticle = article.addComment(text, author); assertThat(updatedArticle.isRight()).isTrue(); assertComment(updatedArticle.get().getComments().head(), text, author); } @Test void in_an_article_containing_existing_ones() { val newText = "Finibus Bonorum et Malorum"; val newAuthor = "Al Capone"; val updatedArticle = article.addComment(text, author) .map(a -> a.addComment(newText, newAuthor)) .flatMap(r -> r); assertThat(updatedArticle.isRight()).isTrue(); assertThat(updatedArticle.get().getComments()).hasSize(2); assertComment(updatedArticle.get().getComments().last(), newText, newAuthor); } } @Nested class return_an_error { @Test void when_adding_an_existing_comment() { val updatedArticle = article.addComment(text, author) .map(a -> a.addComment(text, author)) .flatMap(r -> r); assertThat(updatedArticle.isLeft()).isTrue(); assertThat(updatedArticle.getLeft()) .hasSize(1) .allMatch(error -> error.getDescription().equals("Comment already in the article")); } } }

Slide 46

Slide 46 text

Anti-Patterns : Comment out failing tests A test that fails is a feedback loop for us Always apply these rules: q Has a regression been found by the test? Fix the code! q Is one of the assumptions of the test no longer valid? Delete it!! q Has the application really changed the functionality under test for a valid reason? Update the test!! Never comment out failing tests

Slide 47

Slide 47 text

Anti-Patterns : The hunt to 100% code coverage

Slide 48

Slide 48 text

Code Coverage A coverage metric shows how much source code a test suite executes, from none to 100% Can’t be used to effectively measure the quality of a test suite • Too little coverage in your code base -> 10% : • Demonstrate you are not testing enough • The reverse isn’t true: • Even 100% coverage isn’t a guarantee that you have a good-quality test suite Coverage metrics are a good negative indicator but a bad positive one 𝐶𝑜𝑑𝑒 𝑐𝑜𝑣𝑒𝑟𝑎𝑔𝑒 = Lines of code executed 𝑇𝑜𝑡𝑎𝑙 𝑛𝑢𝑚𝑏𝑒𝑟 𝑜𝑓 𝑙𝑖𝑛𝑒𝑠

Slide 49

Slide 49 text

Code Coverage - demo 𝐶𝑜𝑑𝑒 𝑐𝑜𝑣𝑒𝑟𝑎𝑔𝑒 = Lines of code executed − 2 𝑇𝑜𝑡𝑎𝑙 𝑛𝑢𝑚𝑏𝑒𝑟 𝑜𝑓 𝑙𝑖𝑛𝑒𝑠 − 3 public static boolean isLong(String input) { if(input.length() > 5) { return true; } return false; } class DemoTests { @Test void should_return_false_for_abc() { assertThat(Demo.isLong("abc")).isFalse(); } }

Slide 50

Slide 50 text

Code Coverage – refactor the code 𝐶𝑜𝑑𝑒 𝑐𝑜𝑣𝑒𝑟𝑎𝑔𝑒 = Lines of code executed − 3 𝑇𝑜𝑡𝑎𝑙 𝑛𝑢𝑚𝑏𝑒𝑟 𝑜𝑓 𝑙𝑖𝑛𝑒𝑠 − 3 Test still verifies the same number of possible outcomes… But we are now at 100% coverage class DemoTests { @Test void should_return_false_for_abc() { assertThat(Demo.isLong("abc")).isFalse(); } } public static boolean isLong(String input) { return input.length() > 5; }

Slide 51

Slide 51 text

Any alternative ?

Slide 52

Slide 52 text

Branch Coverage Focuses on control structures : if, match statements Shows how many of such control structures are traversed by at least one test in the suite 𝐵𝑟𝑎𝑛𝑐ℎ 𝑐𝑜𝑣𝑒𝑟𝑎𝑔𝑒 = 𝐵𝑟𝑎𝑛𝑐ℎ𝑒𝑠 𝑡𝑟𝑎𝑣𝑒𝑟𝑠𝑒𝑑 𝑇𝑜𝑡𝑎𝑙 𝑛𝑢𝑚𝑏𝑒𝑟 𝑜𝑓 𝑏𝑟𝑎𝑛𝑐ℎ𝑒𝑠 2 paths length > 5 length <= 5 50% coverage here class DemoTests { @Test void should_return_false_for_abc() { assertThat(Demo.isLong("abc")).isFalse(); } } public static boolean isLong(String input) { return input.length() > 5; }

Slide 53

Slide 53 text

Branch Coverage in IntelliJ • Sampling : collecting line coverage with negligible slowdown • Tracing : enables the accurate collection of the branch coverage

Slide 54

Slide 54 text

Enough to check our test quality ?

Slide 55

Slide 55 text

class DemoTests { @Test void should_return_false_for_abc() { assertThat(Demo.isLong("abc")); } } Problems with coverage We can’t guarantee that the test verifies all the possible outcomes of the system under test No coverage metric can consider code paths in external libraries Unit tests must have appropriate assertions Assertion-free testing

Slide 56

Slide 56 text

Mutation Testing Test our tests by introducing MUTANTS (fault) into our production code during the test execution : To check that the test is failing If the test pass, there is an issue

Slide 57

Slide 57 text

Best practices ? • Tests should test one thing only (one behavior) • Create more specific tests to drive more generic solution (triangulate) • Give your tests meaningful names (behavior/goal-oriented names) that reflect your business domain • Always see the test failes for the right reason Ensure you have meaningful feedback from failing tests • Keep your tests and production code separate Organize your tests to reflect your production code (similar project structure)

Slide 58

Slide 58 text

Other testing topics • Test Driven Development : Test first development approach • Approval Testing : Less assertions / better quality • Mutation Testing : Measure the quality of your teste • Property-Based Testing : Write less tests for a better coverage • Consumer-Driven Contract Testing • Behavior-Driven Development • Acceptance Test Driven Development • …

Slide 59

Slide 59 text

Conclusion • What is the most valuable stuff you have learned today ? • What do you need to apply those concepts in your day to day ? • Think about the latest tests you wrote, what could be improved in it ? • On the last problem you have encountered, how Unit Tests could have helped you ?

Slide 60

Slide 60 text

To Go Further Unit Testing Principles, Practices and Patterns - Vladimir Khorikov