Test Plan con i superpoteri: Mocking
Mattia Tommasone
@raibaz
About me
- 10+ years spent writing (and reading) code
- FLOSS Enthusiast
- Former:
- Developer Advocate @ Google
- Kids' football coach
- University lecturer
- Conference speaker
- Current:
- Lead developer @ Brandon Group
- Maintainer for MockK
A cosa serve una test suite?
- Ad avere le idee chiare su cosa deve fare (o non fare) il codice che scriviamo
- A stanare dei corner case che non erano evidenti
- A sapere che non stiamo rompendo niente di quello che c'era già
- Al te stesso di tra sei mesi
Disclaimer
Testing sui libri
class Adder {
fun add(op1: Int, op2: Int) {
return op1 + op2
}
}
Nel mondo reale però...
- Connessioni a DB
- Connessioni HTTP
- Servizi esterni
- Librerie di terze parti
- Interazioni non banali tra componenti
- Side effects
class BusinessLogicService(
private val persistenceService: MyPersistenceService
) {
fun doSomethingRelevant(objectId: Long, someValue: Int) {
val myObject = persistenceService.retrieveObject(objectId)
?: throw IllegalArgumentException("$objectId not found")
myObject.someProperty = someValue
persistenceService.saveObject(myObject)
}
}
perché è difficile da testare?
- Potresti non avere uno storage a portata di mano
- La persistenza è al di fuori dello scope di questo componente
esattamente, cosa voglio testare?
- Le condizioni di errore: cosa succede quando l'oggetto che voglio aggiornare non esiste?
- L'happy path: se esiste, voglio testare che venga salvato con il valore aggiornato
Serve un mock!
Ma cos'è un mock?
due superpoteri in uno
- Mi permette di guidare il contesto attorno al mio oggetto in modo che si comporti in un modo predefinito e noto
- Mi permette di verificare che il mio oggetto si comporti nel modo che mi aspetto rispetto al contesto che gli sta attorno
class BusinessLogicServiceTest {
@MockK
private lateinit var persistenceService : MyPersistenceService
private lateinit var service : BusinessLogicService
@Before
fun setup() {
MockKAnnotations.init(this)
service = BusinessLogicService(persistenceService)
}
@Test
fun `doing something with a non-existing object throws an exception`() {
every {
persistenceService.retrieveObject(any())
} returns null
assertFailsWith<IllegalArgumentException> {
service.doSomethingRelevant(123L, 345)
}
}
}
@Test
fun `doing something with an existing object updates it and saves it`() {
every {
persistenceService.retrieveObject(any())
} returns MyClass()
service.doSomethingRelevant(123L, 345)
verify {
persistenceService.saveObject(any())
}
}
@Test
fun `doing something with an existing object updates it and saves it`() {
every {
persistenceService.retrieveObject(any())
} returns MyClass()
service.doSomethingRelevant(123L, 345)
val slot = slot<MyClass>()
verify {
persistenceService.saveObject(capture(slot))
}
assertEquals(345, slot.captured.someProperty)
}
perché è così potente?
permette di testare le interazioni (i contratti) tra oggetti
permette di astrarre da entità che potrebbero non esserci durante i test
testare le interazioni può aiutare a far emergere problemi nel design
può essere d'aiuto a fare Cqrs
non è solo per il codice object-oriented
c'è anche per il mio linguaggio?
phake.readthedocs.io
docs.python.org/3/library/unittest.mock.html
https://github.com/mockk/mockk
devcast-codemotion
By Mattia Tommasone
devcast-codemotion
- 233