WHAT IS PURE FUNCTIONAL PROGRAMMING, AND HOW IT
CAN IMPROVE OUR APPLICATION TESTING?
Voxxed Days Zürich 2016-03-03
Voxxed Days Zürich 2016-03-03 1
Table of Content• What is Pure Func0onal Programming?
• Tes0ng
• Type Systems
• Conclusions
• Q & A
Voxxed Days Zürich 2016-03-03 6
What is Func,onal Programming?• Func&ons are first-class, that is, func&ons are values which can
be used in exactly the same ways as any other sort of value.
• FP languages are focused around evalua&ng expressions rather than execu&ng instruc&ons.
Voxxed Days Zürich 2016-03-03 7
What is Referen-al Transparency?
“An expression is said to be referen1ally transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and
output on the same input).”
Voxxed Days Zürich 2016-03-03 9
Referen&al Transparency in Haskell
sum :: Int -> Int -> Intsum a b = a + b
sum 2 3 == 5sum 3 3 == 6
Voxxed Days Zürich 2016-03-03 10
Referen&al Transparency in Javapublic int sum(int a, int b) { int anotherB = // If the DB is empty, use b as default persistence.readFromSumTableWithDefault(b);
int sum = a + anotherB;
updateSumTable(sum);
return sum;}
sum(2, 3) == 5sum(2, 3) == 7 // ?!
Voxxed Days Zürich 2016-03-03 11
An Impure language can write a pure func2on
/* This is the same method as before */public int sum(int a, int b) { return a + b;}
sum(2, 3) == 5sum(2, 3) == 5
Voxxed Days Zürich 2016-03-03 12
This is not a pure func0on
public int sum(int a, int b) { writeLastExecutedFile(a, b); return a + b;}
Voxxed Days Zürich 2016-03-03 13
What are the "Pure" Func.onal programming languages?
• Haskell
• Clean
• Excel
• Scala?
• Java?
Voxxed Days Zürich 2016-03-03 14
Untestable Cafeclass CreditCard() { def charge(price: BigDecimal): Unit = if (Random.nextInt(4) == 1) println("I'm calling a webservice") else throw new RuntimeException("crashing")}
case class Coffee(price: BigDecimal = 1)
class UntestableCafe { def buyCoffee(cc: CreditCard): Coffee = {
val cup = new Coffee() cc.charge(cup.price) // Side effect cup }}
Voxxed Days Zürich 2016-03-03 17
Untestable Cafe Test
class UntestableCafeTest extends Specification { "Cafe should be able to charge an amount" >> {
val cafe = new UntestableCafe()
val coffee = cafe.buyCoffee(new CreditCard())
success("hopefully charged") }}
Voxxed Days Zürich 2016-03-03 18
Testable Cafecase class CreditCard()case class Coffee(price: BigDecimal = 1)
trait Payments { def charge(cc: CreditCard, amount: BigDecimal) }
class TestableCafe { def buyCoffee(cc: CreditCard, p: Payments): Coffee = {
val cup = new Coffee()
p.charge(cc, cup.price) cup }}
Voxxed Days Zürich 2016-03-03 19
Testable Cafe Testclass TestableCafeTest extends Specification with Mockito { "Cafe should be able to charge an amount" >> {
val cafe = new TestableCafe()
val mockedPayment = mock[Payments] val cc = new CreditCard()
val coffee = cafe.buyCoffee(cc, mockedPayment)
there was one(mockedPayment).charge(cc, 1) }}
Voxxed Days Zürich 2016-03-03 20
Pure Cafecase class CreditCard()
case class Coffee(price: BigDecimal = 1)
case class Charge(cc: CreditCard, amount: BigDecimal)
class PureCafe { def buyCoffee(cc: CreditCard): (Coffee, Charge) = {
val cup = new Coffee()
(cup, Charge(cc, cup.price)) }}
Voxxed Days Zürich 2016-03-03 21
Pure Cafe Test
class PureCafeTest extends Specification with Mockito { "Cafe should be able to charge an amount" >> {
val cafe = new PureCafe()
val result = cafe.buyCoffee(new CreditCard())
result._2.amount === 1 }}
Voxxed Days Zürich 2016-03-03 22
Increase the entropy of the inputs"insert element in database" in { val username = randomString
val numberOfRowInserted = database.insert(1, username).futureAwait
val usernameFromDB = database.findById(1)
numberOfRowInserted === 1 usernameFromDB === username}
Voxxed Days Zürich 2016-03-03 25
Property based tes-ng
val modelGenerator = for { tag <- arbitrary[String] elem <- arbitrary[String] } yield Model(tag, elem)
property("String is always 891 characters") = forAll(modelGenerator) { record => processString(record).length == 891}
Voxxed Days Zürich 2016-03-03 27
Beware of readability 1
take5 :: [Char] -> [Char]take5 = take 5 . filter (`elem` ['a'..'e'])
quickCheck ((\s -> (reverse.reverse) s == s) :: [Char] -> Bool)
quickCheck (\s -> all (`elem` ['a'..'e']) (take5 s))
1 h$ps://wiki.haskell.org/Introduc9ontoQuickCheck1
Voxxed Days Zürich 2016-03-03 30
TDD by Kent Beck and property base tes3ng 2
2 h$p://natpryce.com/ar2cles/000807.html
Voxxed Days Zürich 2016-03-03 31
Code Example with a lot of mocking public void mocking() { final File dir = Mockito.mock(File.class); BDDMockito.given(dir.getAbsolutePath()).willReturn("/some/otherdir/" + FILE); Mockito.doReturn(dir).when(extensionAccessor).getExtensionDir(FILE);
final String remotePath = "/some/dir/" + NAME; Mockito.doReturn(remotePath).when(request).getRequestURI();
Mockito.doReturn(remotePath).when(filter).getFullPathNameFromRequest(request); Mockito.doReturn("/some/dir/" + NAME).when(filter).getFullPathNameFromRequest(request);
filter.doFilter(request, response, filterChain);
Mockito.verify(filter).copyFileInternalIfNeeded("/some/dir/" + NAME); }
Voxxed Days Zürich 2016-03-03 33
Mockable classtrait DbThing { def getDbStuff(params): Future[DbStuff] }
trait HttpThing { def getHttpStuff(params): Future[HttpStuff] }
class BizThingImpl(db: DbThing, http: HttpThing) { def doComplicatedStuff(params): () = db.getDbStuff(params).zip(http.getHttpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) doSomething else doSomethingElse case _ => throw new Exception("something went wrong") }}
Voxxed Days Zürich 2016-03-03 34
Can we avoid stubbing?def bizThingImpl(getDBStuff: Param => Future[DBStuff]) (httpStuff: Param => Future[HTTPStuff]): () = {
getDbStuff.zip(httpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) doSomething else doSomethingElse case _ => throw new Exception("something went wrong") }}
Voxxed Days Zürich 2016-03-03 35
How can we avoid mocking?def bizThingImpl(getDBStuff: Param => Future[DBStuff]) (httpStuff: Param => Future[HTTPStuff]): Future[Result] = {
getDbStuff.zip(httpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) Ok(httpValue.result) else NotFound case _ => InternalServerError }}
Voxxed Days Zürich 2016-03-03 36
Applica'on with li.le logicpublic void complexInteraction(User user) {
httpClient.call(user.firstName , user.lastName, user.address, user.capCode, user.phoneNumber)
db.insertUser(user.firstName , user.lastName, user.address, user.capCode, user.phoneNumber)}
Voxxed Days Zürich 2016-03-03 38
Tes$ng applica$on with li1le logicpublic void testComplexInteraction(User user) {
User user = aUser("firstName", "lastName", "address", "capCode", "phoneNumber")
service.complexInteraction(user)
verify(httpClient) .call(user.firstName, user.lastName, user.address , user.capCode, user.phoneNumber) verify(db) .insertUser(user.firstName , user.lastName, user.address , user.capCode, user.phoneNumber)
}
Voxxed Days Zürich 2016-03-03 39
Referen&al Transparency again 3
function sum(a, b) = { ??? }
num = sum(2,3)
test.assert(typeof num === 'number');
3 h$p://unitjs.com/
Voxxed Days Zürich 2016-03-03 43
Same test using Javapublic String sayHello(String name) { return "Hello " + name + "!";}
public void testGreetingIsString() { assertEquals(String.class, sayHello("luca").getClass());}
Voxxed Days Zürich 2016-03-03 44
Compile )me vs Execu)on )me
• Type System ⟶ Compile /me
• Tests ⟶ Run Time
Voxxed Days Zürich 2016-03-03 47
Compiler error messages shouldn't be hard (Elm) 4
4 h$p://elm-lang.org/blog/compiler-errors-for-humans
Voxxed Days Zürich 2016-03-03 49
"The problem with the type checker is that it should never be smarter
than the developer"— Unknown
Voxxed Days Zürich 2016-03-03 50
Readability• Use unit tes*ng to give example of your API
• Use property based tes*ng when you want to verify proper*es
• Use Type Systems when having li?le logic
Voxxed Days Zürich 2016-03-03 51
Test the concatena+ons of two lists
def testConcatenationOfList() { List(1, 2) ++ List(3,4) === List(1,2,3,4)}
def testConcatenationOfListWithSize() { val sum = List(1, 2) ++ List(3,4)
sum.length === 4}
Voxxed Days Zürich 2016-03-03 55
Concatena(on using dependent types 5
concatenate : Vect n a -> Vect m a -> Vect (n + m) aconcatenate Nil ys = ysconcatenate (x :: xs) ys = x :: app xs ys
5 h$ps://eb.host.cs.st-andrews.ac.uk/wri8ngs/idris-tutorial.pdf
Voxxed Days Zürich 2016-03-03 56
Idris error messages
Can’t unify
Vect (n + n) awith
Vect (plus n m) aSpecifically:
Can’t unify plus n n with plus n m
Voxxed Days Zürich 2016-03-03 57
Conclusion• Pure Func*onal Programming leads to a more testable code
• If you're going to use pure FP, use a strong type system too (but don't forget tests)
• Modern type systems help us finding bugs in a more efficient way while designing our applica*on.
• If the logic is limited, rely on the Type System
Voxxed Days Zürich 2016-03-03 58
Thank you for your ,meFeedback please!
@volothamp
mailto:[email protected]
Voxxed Days Zürich 2016-03-03 60