└── readme.md /readme.md: -------------------------------------------------------------------------------- 1 | WORKING EFFECTIVELY WITH UNIT TESTS 2 | 3 | 4 | - [Types of tests](#types-of-tests) 5 | - [State Verification](#state-verification) 6 | - [Behavior Verification](#behavior-verification) 7 | - [Unit Tests](#unit-tests) 8 | - [Improving Assertions](#improving-assertions) 9 | - [One Assertion Per Test.](#one-assertion-per-test) 10 | - [Implementation Overspecification](#implementation-overspecification) 11 | - [Assert Last](#assert-last) 12 | - [Expect Literals](#expect-literals) 13 | - [Negative Testing](#negative-testing) 14 | - [Improving Test Cases](#improving-test-cases) 15 | - [Too Much Magic](#too-much-magic) 16 | - [Inline Setup](#inline-setup) 17 | - [Test Names](#test-names) 18 | - [Improving Test Suites](#improving-test-suites) 19 | - [Separating The Solitary From The Sociable](#separating-the-solitary-from-the-sociable) 20 | - [Questionable Tests](#questionable-tests) 21 | - [Custom Assertions](#custom-assertions) 22 | - [Global Definition](#global-definition) 23 | 24 | 25 | # Types of tests 26 | ## State Verification 27 | Assert the expected state of the object and/or collaborators. 28 | ```java 29 | public class RentalTest { 30 | @Test 31 | public void rentalIsStartedIfInStore() { 32 | Movie movie = a.movie.build(); 33 | Rental rental = a.rental.w(movie).build(); 34 | Store store = a.store.w(movie).build(); 35 | rental.start(store); 36 | assertTrue(rental.isStarted()); 37 | assertEquals(0, store.getAvailability(movie)); 38 | } 39 | } 40 | ``` 41 | `Rental` is the _Subject Under Test_ (SUT) or _Class Under Test_ (CUT) and `Store` a collaborator 42 | 43 | State verification tests generally rely on assertions to verify the state of our objects. 44 | 45 | ## Behavior Verification 46 | 47 | The test expect to generate specific interactions between objects. 48 | 49 | ```java 50 | 51 | public class RentalTest { 52 | @Test 53 | public void rentalIsStartedIfInStore() { 54 | Movie movie = a.movie.build(); 55 | Rental rental = a.rental.w(movie).build(); 56 | Store store = mock(Store.class); 57 | when(store.getAvailability(movie)).thenReturn(1); 58 | rental.start(store); 59 | assertTrue(rental.isStarted()); 60 | verify(store).remove(movie); 61 | } 62 | } 63 | 64 | ``` 65 | 66 | ## Unit Tests 67 | 68 | ### Solitary Unit Test 69 | 70 | Unit test at the class level, but: 71 | 72 | 1. Never cross boundaries 73 | 2. The Class Under Test should be the only concrete class 74 | found in a test. 75 | 76 | ```java 77 | 78 | public class MovieTest { 79 | @Test 80 | (expected=IllegalArgumentException.class) 81 | public void invalidTitle() { 82 | a.movie.w(UNKNOWN).build(); 83 | } 84 | } 85 | ``` 86 | 87 | ### Sociable Unit Test 88 | Any Unit Test that cannot be classified as a Solitary Unit Test is a Sociable 89 | Unit Test. 90 | 91 | These tests have 2 potential issues: 92 | * They run the risk of failing due to an implementation change in a collaborator. 93 | * When they fail it can be hard to determine if the issue 94 | is coming from the Class Under Test, a collaborator, or 95 | somewhere else completely. 96 | 97 | Mitigate the above issues with the following suggestions. 98 | * Verify as much as you can with 1 happy path test per method. When things do go wrong, you want as little noise as possible. Limiting the number of Sociable Unit 99 | Tests can go a long way to helping the situation when things go wrong. 100 | * If you stick to fixing the Solitary Unit Tests before the Sociable Unit Tests, by the time you get to a failing Sociable Unit test you should have a very good idea where to find the root of the problem. 101 | 102 | # Improving Assertions 103 | 104 | ## One Assertion Per Test. 105 | Test Naming should express the intent of the test. 106 | 107 | * If your test has an assertion, do not add any mock 108 | verifications. 109 | * If your test verifies a mock, do not add any assertions. 110 | * At most, 1 assertion per test. 111 | * At most, 1 mock verification per test. 112 | * When stubbing method return values, use the most 113 | generic argument matcher possible. 114 | 115 | ## Implementation Overspecification 116 | The more specification your tests contain the more likely you are to create a fragil test suite. 117 | 118 | ### Flexible Argument Matchers 119 | Use Mockito’s `anyInt`, `anyString` and `anyBoolean` 120 | ```java 121 | movie.getTitle(anyString(), anyInt())) 122 | ``` 123 | ### Default Return Values 124 | Return values to avoid a `NullPointerException` however we don't always need a return 125 | 126 | ### Law of Demeter 127 | _Only talk to your immediate friends._ 128 | Reduce or eliminate the relations between the objects. 129 | 130 | ```java 131 | public class CustomerTest { 132 | @Test 133 | public void recentRentals2Rental() { 134 | assertEquals( 135 | "Recent rentals:\nnull\nnull", 136 | a.customer.w( 137 | mock(Rental.class), 138 | mock(Rental.class)).build() 139 | .recentRentals() 140 | ); 141 | } 142 | ... 143 | } 144 | ``` 145 | ### Get Sociable 146 | After having all the unitary tests we can be sociable tests based on them. 147 | 148 | ```java 149 | public class CustomerTest { 150 | ... 151 | @Test 152 | public void recentRentalsWith3OrderedRentals() { 153 | assertEquals( 154 | "Recent rentals:"+ 155 | "\nGodfather 4\nLion King\nScarface", 156 | a.customer.w( 157 | a.rental.w(a.movie.w("Godfather 4")), 158 | a.rental.w(a.movie.w("Lion King")), 159 | a.rental.w(a.movie.w("Scarface")), 160 | a.rental.w(a.movie.w("Notebook"))) 161 | .build().recentRentals() 162 | ); 163 | } 164 | } 165 | ``` 166 | 167 | ## Assert Last 168 | 169 | The assertion should be the last piece of code found within a test. 170 | 171 | ### “Arrange-Act-Assert” 172 | 1. Arrange all necessary preconditions 173 | and inputs. 174 | 2. Act on the object or method 175 | under test. 176 | 3. Assert that the expected results have occurred. 177 | 178 | **Assert Last** is where we should focus if we can not get the 3 As. 179 | 180 | ### Expect Exceptions via Try/Catch 181 | 182 | ```java 183 | public class MovieTest { 184 | @Test 185 | public void invalidTitle() { 186 | Exception e = null; 187 | try { 188 | a.movie.w(UNKNOWN).build(); 189 | } catch (Exception ex) { 190 | e = ex; 191 | } 192 | assertEquals( 193 | IllegalArgumentException.class, 194 | e.getClass() 195 | ); 196 | } 197 | } 198 | ``` 199 | This is not perfect. Better here 👇 200 | 201 | ### Assert Throws 202 | ```java 203 | public class MovieTest { 204 | 205 | @Test 206 | public void invalidTitle() { 207 | Runnable runnable = new Runnable() { 208 | public void run() { 209 | a.movie.w(UNKNOWN).build(); 210 | } 211 | }; 212 | assertThrows( IllegalArgumentException.class, runnable); 213 | } 214 | 215 | public void assertThrows( 216 | Class ex, Runnable runnable) { 217 | Exception exThrown = null; 218 | try { 219 | runnable.run(); 220 | } catch (Exception exThrownActual) { 221 | exThrown = exThrownActual; 222 | } 223 | if (null == exThrown) 224 | fail("No exception thrown"); 225 | else 226 | assertEquals(ex, exThrown.getClass()); 227 | } 228 | } 229 | ``` 230 | This could be simplified using JAVA 8 lambdas or kotlin 231 | 232 | ## Expect Literals 233 | 234 | The expected value should be the literal itself, not a variable. 235 | In case of money, dates, or similars try to convert them to literals 236 | 237 | ## Negative Testing 238 | Are tests that assert something did not happen. Don't do it. 239 | 240 | ### Just Be Sociable 241 | Ask yourself : 242 | 243 | > Why is it hard to get positive ROI out of this test? 244 | 245 | > Why am I testing this interaction in the first place? 246 | 247 | You will probably end with a different test or tests. 248 | 249 | # Improving Test Cases 250 | An effective test suite implies maintainable test cases. 251 | 252 | Tests are procedural by nature. 253 | 254 | * The primary motivation for naming a test method is documentation 255 | * Instance method collaboration is considered an antipattern. 256 | * Each instance method is magically given its own set of instance variables 257 | * Each test method should encapsulate the entire lifecycle and verification, independent of other test methods. 258 | * Tests methods should have very specific goals which can be easily identified by maintainers. 259 | 260 | ## Too Much Magic 261 | Remove complex code when implementing tests. They should be simple and straightforward to understand. 262 | 263 | ## Inline Setup 264 | If you aspire to create tiny universes with minimal conceptual 265 | overhead, rarely will you find the opportunity to use Setup( `@Before` ). 266 | 267 | ### Similar Creation and Action 268 | Reduce creation duplication by introducing globally used builders. 269 | 270 | Duplicate code is a smell. Setup and Teardown are deodorant. 271 | 272 | ### Setup As An Optimization 273 | Creating a database connection per Sociable Unit Test would be slower than creating one in each test within a single Test Case. 274 | > Why are we creating more than one database connection at all? 275 | 276 | > Why not create one global connection and run each test in a transaction that’s automatically rolled back 277 | after each Sociable Unit Test? 278 | 279 | Answer yourself these questions 280 | 281 | * Are all the Sociable Unit Tests still necessary? 282 | * Are the interactions with the File System, Database, 283 | and/or Messaging System still necessary? 284 | * Is there a faster way to accomplish any of the tasks 285 | setting the File System, Database and/or Messaging 286 | System back to a known state? 287 | 288 | ## Test Names 289 | Test names are like comments. 290 | > Code never lies, comments sometimes do 291 | 292 | # Improving Test Suites 293 | ## Separating The Solitary From The Sociable 294 | 1. Sociable Unit Tests can be slow and nondeterministic 295 | 2. Sociable Unit Tests are more susceptible to cascading 296 | failures 297 | 298 | ### Increasing Consistency And Speed With Solitary Unit Tests 299 | What affects the more in tests speed: 300 | * Interacting with a database 301 | * Interacting with the filesystem 302 | * Interacting with time 303 | 304 | #### Database and Filesystem Interaction 305 | Wrapping the commonly used libraries with a gateway that provides the following capabilities: 306 | 307 | * The ability to disallow access within Solitary Unit Tests 308 | * The ability to reset to a base state before each Sociable 309 | Unit Test. 310 | 311 | ```java 312 | public class FileWriterGateway extends FileWriter { 313 | public static boolean disallowAccess = false; 314 | public FileWriterGateway(String filename) throws IOException { 315 | super(filename); 316 | if (disallowAccess) throw new RuntimeException("access disallowed"); 317 | } 318 | } 319 | ``` 320 | 321 | ```java 322 | public class Solitary { 323 | @Before 324 | public void setup() { 325 | FileWriterGateway.disallowAccess = true; 326 | } 327 | } 328 | ``` 329 | 330 | ```java 331 | public class PidWriterTest extends Solitary { 332 | @Test 333 | public void writePid() throws Exception { 334 | RuntimeMXBean bean = mock(RuntimeMXBean.class); 335 | when(bean.getName()).thenReturn("12@X"); 336 | FileWriterGateway facade = mock(FileWriterGateway.class); 337 | PidWriter.writePid(facade, bean); 338 | verify(facade).write("12"); 339 | } 340 | } 341 | ``` 342 | 343 | ### Using Speed To Your Advantage 344 | Convert a Sociable Unit Tests to a Solitary Unit Tests provides approximately the same ROI. 345 | 346 | >Faster feedback of equal quality. 347 | 348 | Always run all of the Solitary Unit Tests first, and run the Sociable Unit Tests if and only if all of the Solitary Unit Tests pass. 349 | 350 | ### Avoiding Cascading Failures With Solitary Unit Tests 351 | 1. Sociable Unit Tests are more susceptible to cascading failures 352 | 2. Few things kill productivity and motivation faster than cascading test failures. 353 | 354 | ## Questionable Tests 355 | 356 | * Don't Test Language Features or Standard Library Classes 357 | * Don't Test Framework Features or Classes 358 | * Don't Test Private Methods 359 | 360 | ## Custom Assertions 361 | T ake assert structural duplication and replace it with a concise, globally useful single assertion. 362 | 363 | > **Structural Duplication**: The overall pattern of the code is the same, but the details differ. 364 | 365 | ```java 366 | @Test 367 | public void invalidTitleCustomAssertion() { 368 | assertThrows( 369 | IllegalArgumentException.class, 370 | () -> a.movie.w(UNKNOWN).build()); 371 | } 372 | ``` 373 | 374 | ### Custom Assertions on Value Objects 375 | Make them if there are several assertions that focused on a non Literal type like date. 376 | 377 | ```java 378 | public static void assertDateWithFormat(String expected, 379 | String format, 380 | Date date ){ 381 | assertEquals(expected, new SimpleDateFormat(format).format(date)); 382 | } 383 | ``` 384 | ## Global Definition 385 | > If you find yourself repeating the same idea in 386 | multiple test cases, look for a higher level concept that can 387 | be extracted and reused. 388 | 389 | ### Object Mother 390 | An object with a fixture of the data used for a test. 391 | 392 | **CONS**: As the project grows the coupling between the tests and the objects grows bigger and errors start to appear. 393 | 394 | ### Test Data Builders 395 | 396 | For each class you want to use in a test, create a 397 | Builder for that class that: 398 | * Has an instance variable for each constructor parameter 399 | * Initializes its instance variables to commonly used or safe values 400 | * Has a build method that creates a new object using the values in its instance variables 401 | * Has “chainable” public methods for overriding the values in its instance variables. 402 | 403 | **PROS**: Data Builders provide you the benefits of creating an object with sensible defaults, and provide methods for adding your 404 | test specific data - thus keeping your tests decoupled. 405 | 406 | ### Test Data Builder Syntax 407 | Choose one an use the same always. 408 | 409 | 1. `anOrder().from(aCustomer().with(...)).build();` 410 | 2. `a.order.w(a.customer.w(...)).build();` 411 | 3. `build(order.w(customer.w(...)));` 412 | 413 | ### Creating Stubs 414 | 415 | #### Create, Stub, Return 416 | 417 | 1. Create a stub. 418 | 2. Stub the result of a single method call. 419 | 3. Pass the stub to another method. --------------------------------------------------------------------------------