Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

What's the Fuzz about Unit Testing

What's the Fuzz about Unit Testing

All my tests used to involve users named Alice and Bob. Aged 42. Working at FooBar Inc. The problem when using hard-coded values in your tests, means that you only test one single path of your code. Ever. During this talk, you will discover a fuzzy testing library to make your test use a wider range of data.

Xavier Gouchet

October 25, 2018
Tweet

More Decks by Xavier Gouchet

Other Decks in Programming

Transcript

  1. About me… • Xavier F. Gouchet • Writing Android apps

    since Android 1.5 (Cupcake) • Lead Android Engineer at WorkWell • @xgouchet on Github / StackOverflow / LinkedIn
  2. The test method… @Test fun testItemClicked() { val user =

    User(id = 42 /* … */) testedPresenter.onUserSelected(user) verify(mockView).navigateToChat(user.id) }
  3. … and the test setup method @Before fun setUp() {

    val company = Company(id = 42 /* … */) testedPresenter = Presenter(company) testedPresenter.view = mockView }
  4. When all your fake data are “foo” and 42, how

    can you be sure that your tests are valid ?
  5. The problem when using static test data • Where does

    the 42 come from ? • Lots of hardcoded values ! • Only one value is tested. Ever.
  6. What we can do about it • Keep track of

    test values ? • Parameterized Tests ? • Random test data !
  7. Using fuzzy data in your tests • Random data •

    Help define boundaries of valid / invalid inputs • Help find edge cases
  8. With fuzz @Test fun invalidateDataAfterTimeout() { val fakeTS = rand.nextLong()

    val fakeTTL = rand.nextLong() val fakeDelay = rand.nextLong() testedObject.setLastCallTimestamp(fakeTS) testedObject.setDataTTL(fakeTTL) val ts = fakeTS + fakeTTL + fakeDelay val result = testedObject.isDataValid(ts) assertFalse(result) }
  9. Elmyr forging numbers forger.anInt(min = 0, max =100) forger.aSmallInt() forger.aPositiveLong()

    forger.aGaussianFloat(mean = 42f, standardDeviation = 100f) forger.aDoubleArray(DoubleConstraint.NEGATIVE_STRICT)
  10. Elmyr forging (from) Collections forger.aList { aUrl() } forger.aSubListOf(myList) forger.anElementFrom(myCollection)

    forger.anElementFrom("foo", "bar", "baz" /* … */) forger.aValueFrom(MyEnum::class) forger.aNullableFrom { aUrl() }
  11. Elmyr forging (from) Collections forger.aList { aUrl() } forger.aSubListOf(myList) forger.anElementFrom(myCollection)

    forger.anElementFrom("foo", "bar", "baz" /* … */) forger.aValueFrom(MyEnum::class) forger.aNullableFrom { aUrl() }
  12. Without fuzz @Test fun invalidateDataAfterTimeout() { val fakeTS = forger.aTimestamp()

    val fakeTTL = forger.aLong(1, 86400000) val fakeDelay = forger.aLong(1, 86400000) testedObject.setLastCallTimestamp(fakeTS) testedObject.setDataTTL(fakeTTL) val ts = fakeTS + fakeTTL + fakeDelay val result = testedObject.isDataValid(ts) assertFalse(result) }
  13. <FooTest.testSomething()> failed with fake seed 0x4815162342 Add the following line

    in your setUp method to reproduce : forger.reset(0x4815162342L) Elmyr libary
  14. Custom POJO / Data Class data class User( val id:

    String, val firstName: String, val lastName: String, val jobTitle: String?, val avatarUrl: String?, val email: String?, val tags: List<String>? )
  15. Custom extension function fun Forger.aUser(): User { return User( id

    = aNumericalString(), firstName = aWord(Case.CAPITALIZE), lastName = aWord(Case.CAPITALIZE), jobTitle = aNullable { aSentence() }, avatarUrl = aNullable { aUrl() }, email = aNullable { anEmail() }, tags = aList { aWord() } ) }
  16. Our original test… @Before fun setUp() { val company =

    forger.aCompany() testedPresenter = Presenter(company) testedPresenter.view = mockView } @Test fun testItemClicked() { val user = forger.aUser() testedPresenter.onUserSelected(user) verify(mockView).navigateToChat(user.id) }
  17. “You need to be as confident in the tests we

    code as we are in the code we test.” Xavier F. Gouchet