Created
June 1, 2015 07:40
-
-
Save magicly/77c40bc3a4b5483ad8cc to your computer and use it in GitHub Desktop.
intro to scalacheck
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |
<meta name="keywords" content="property-based testing, scalacheck, scala, java, unit testing, jUnit" /> | |
<meta name="description" content="Property-based testing with ScalaCheck." /> | |
<title>Remark</title> | |
<style type="text/css"> | |
@import url(http://fonts.googleapis.com/css?family=Droid+Serif); | |
@import url(http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); | |
@import url(http://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic); | |
body { | |
font-family: 'Droid Serif'; | |
} | |
h1, h2, h3 { | |
font-family: 'Yanone Kaffeesatz'; | |
font-weight: 400; | |
margin-bottom: 0; | |
} | |
.remark-slide-content h1 { font-size: 3em; } | |
.remark-slide-content h2 { font-size: 2em; } | |
.remark-slide-content h3 { font-size: 1.6em; } | |
.footnote { | |
position: absolute; | |
bottom: 3em; | |
} | |
li p { line-height: 1.25em; } | |
.red { color: #fa0000; } | |
.large { font-size: 2em; } | |
a, a > code { | |
color: rgb(249, 38, 114); | |
text-decoration: none; | |
} | |
code { | |
-moz-border-radius: 5px; | |
-web-border-radius: 5px; | |
background: #e7e8e2; | |
border-radius: 5px; | |
} | |
.remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; } | |
.remark-code-line-highlighted { background-color: #373832; } | |
.pull-left { | |
float: left; | |
width: 47%; | |
} | |
.pull-right { | |
float: right; | |
width: 47%; | |
} | |
.pull-right ~ p { | |
clear: both; | |
} | |
#slideshow .slide .content code { | |
font-size: 0.8em; | |
} | |
#slideshow .slide .content pre code { | |
font-size: 0.9em; | |
padding: 15px; | |
} | |
.inverse { | |
background: #272822; | |
color: #777872; | |
text-shadow: 0 0 20px #333; | |
} | |
.inverse h1, .inverse h2 { | |
color: #f3f3f3; | |
line-height: 0.8em; | |
} | |
/* Slide-specific styling */ | |
#slide-inverse .footnote { | |
bottom: 12px; | |
left: 20px; | |
} | |
#slide-how .slides { | |
font-size: 0.9em; | |
position: absolute; | |
top: 151px; | |
right: 140px; | |
} | |
#slide-how .slides h3 { | |
margin-top: 0.2em; | |
} | |
#slide-how .slides .first, #slide-how .slides .second { | |
padding: 1px 20px; | |
height: 90px; | |
width: 120px; | |
-moz-box-shadow: 0 0 10px #777; | |
-webkit-box-shadow: 0 0 10px #777; | |
box-shadow: 0 0 10px #777; | |
} | |
#slide-how .slides .first { | |
background: #fff; | |
position: absolute; | |
top: 20%; | |
left: 20%; | |
z-index: 1; | |
} | |
#slide-how .slides .second { | |
position: relative; | |
background: #fff; | |
z-index: 0; | |
} | |
/* Two-column layout */ | |
.left-column { | |
color: #777; | |
width: 20%; | |
height: 92%; | |
float: left; | |
} | |
.left-column h2:last-of-type, .left-column h3:last-child { | |
color: #000; | |
} | |
.right-column { | |
width: 75%; | |
float: right; | |
padding-top: 1em; | |
} | |
.image { | |
padding: 0px; | |
color: white; | |
background-color: black; | |
position: relative; | |
} | |
.image img { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
/*height: 100%;*/ | |
float: left; | |
} | |
.image h1, .image h2 { | |
position: absolute; | |
bottom: 0; | |
text-align: center; | |
left: 20px; | |
} | |
.image-white { | |
color: black; | |
background-color: white; | |
} | |
.image-last h1 { | |
right: 0; | |
bottom: 100px; | |
} | |
</style> | |
</head> | |
<body> | |
<textarea id="source"> | |
name: image | |
layout: true | |
class: center, middle, image | |
--- | |
name: inverse | |
layout: true | |
class: center, middle, inverse | |
--- | |
<img src="images/love-the-bomb.jpg" height="50%" /> | |
#Property-based testing with ScalaCheck | |
--- | |
## Test | |
--- | |
layout: false | |
.left-column[ | |
## Test | |
### Why? | |
] | |
.right-column[ | |
- find bug | |
- quality | |
- ...... | |
] | |
--- | |
.left-column[ | |
## Test | |
### Why? | |
### What? | |
] | |
.right-column[ | |
- white box | |
* Unit | |
* integration | |
* .red[**Property-based**] | |
* ... | |
- black box | |
* user | |
* .red[**QA**] | |
* ... | |
] | |
--- | |
template: inverse | |
## Unit Testing vs Property-based Testing | |
--- | |
.left-column[ | |
## Unit Testing | |
] | |
.right-column[ | |
- [wiki](http://en.wikipedia.org/wiki/Unit_testing) | |
In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use. | |
] | |
--- | |
.left-column[ | |
## Unit Testing | |
] | |
.right-column[ | |
max | |
```Java | |
System.out.println(max(1, 2) == 2); | |
``` | |
```Java | |
assert max(1, 2) == 2; | |
``` | |
[assert trap!](http://lavasoft.blog.51cto.com/62575/43735) | |
```bash | |
java -ea MathUtilTest | |
``` | |
] | |
--- | |
.left-column[ | |
## Unit Testing | |
] | |
.right-column[ | |
- Framework | |
* Hibernate to ORM | |
* jQuery to js | |
* Taf | |
* ... | |
- jUnit | |
```Java | |
@Test | |
public void testMax() { | |
int z = MathUtil.max(1, 2); | |
assertEquals(2, z); | |
} | |
``` | |
] | |
--- | |
.left-column[ | |
## Unit Testing | |
] | |
.right-column[ | |
- jUnit | |
```Java | |
@Test | |
public void testMax2() { | |
int z = MathUtil.max(2, 1); | |
assertEquals(2, z); | |
} | |
@Test | |
public void testMax3() { | |
int z = MathUtil.max(2, 2); | |
assertEquals(2, z); | |
} | |
@Test | |
public void testMax4() { | |
int z = MathUtil.max(2, -1); | |
assertEquals(2, z); | |
} | |
``` | |
Passed. yeah... | |
] | |
--- | |
template: image | |
<img src="images/manual-labour.jpg" /> | |
# Manual labour | |
--- | |
template: inverse | |
<img src="images/jx.jpg" height="100%" /> | |
# Are you sure ok? | |
--- | |
.left-column[ | |
## Unit Testing | |
] | |
.right-column[ | |
```Java | |
System.out.println(MathUtil.max(-2, 1));// 2!!!! | |
``` | |
Failed! oh, no... | |
] | |
--- | |
template: inverse | |
<img src="images/whats_wrong.jpg" height="100%" /> | |
--- | |
template: inverse | |
#[writing the minimal code that will make the test pass](http://www.typemock.com/test-driven-development-tdd/) | |
--- | |
class: middle | |
```Java | |
public static int max(int a, int b) { | |
return 2; | |
} | |
``` | |
--- | |
template: image | |
<img src="images/wtf2.jpg" height="100%" /> | |
--- | |
template: inverse | |
##[The Enterprise Developer From Hell, or "EDFH"](http://fsharpforfunandprofit.com/posts/property-based-testing/) | |
a burned-out, always lazy and often malicious programmer | |
--- | |
template: inverse | |
#More test case? | |
--- | |
class: middle | |
```Java | |
@Test | |
public void testMax2() { | |
int z = MathUtil.max(-2, 1); | |
assertEquals(1, z); | |
} | |
``` | |
--- | |
##hehe... | |
```Java | |
public static int max(int a, int b) { | |
if (a < 0) | |
return b; | |
return 2; | |
} | |
``` | |
--- | |
template: image | |
<img src="images/jx2.jpg" height="100%" /> | |
--- | |
template: image | |
<img src="images/AreYouKidding.jpg" height="100%" /> | |
--- | |
template: inverse | |
# How to fix? | |
--- | |
template: inverse | |
# No Magic Number! | |
--- | |
template: inverse | |
# Random! | |
--- | |
class: middle | |
# Expected Value? | |
```Java | |
@Test | |
public void testMax2() { | |
Random r = new Random(); | |
int a = r.nextInt(); | |
int b = r.nextInt(); | |
int max = MathUtil.max(a, b); | |
* assertEquals(?, max); | |
} | |
``` | |
--- | |
class: middle | |
# Expected Value? | |
```Java | |
@Test | |
public void testMax2() { | |
Random r = new Random(); | |
int a = r.nextInt(); | |
int b = r.nextInt(); | |
int max = MathUtil.max(a, b); | |
int expectedValue = b; | |
if (a > b) { | |
expectedValue = a; | |
} | |
assertEquals(expectedValue, max); | |
} | |
``` | |
--- | |
# Duplicate | |
## [DRY(Don't repeat yourself)](http://en.wikipedia.org/wiki/Don't_repeat_yourself) | |
A bad idea to have your tests duplicate the code that you are testing! | |
## [Code smell](http://en.wikipedia.org/wiki/Code_smell) | |
In computer programming, code smell is any symptom in the source code of a program that possibly indicates a deeper problem. | |
--- | |
template: image | |
<img src="images/duplicate_code.jpg" height="100%" /> | |
--- | |
template: image | |
<img src="images/duplicate_code2.jpg" height="100%" /> | |
--- | |
template: inverse | |
# [Property](http://en.wikipedia.org/wiki/Property) | |
In the abstract, property is that which belongs to or with something, whether as an .red[**attribute**] or as a .red[**component**] of said thing. | |
--- | |
.left-column[ | |
## Property demos | |
] | |
.right-column[ | |
- a: Int + b: Int > a | |
- .red[a: Int + 1 > a] | |
- list.reverse.length == list.length | |
- list.sort.sort == list.sort | |
- (a + b).startsWith(a) | |
- (a + b).length > a.length && (a + b).length > b.length | |
- .red[s.toUpperCase.toLowerCase == s.toLowerCase] | |
- .red[new String(s.getBytes(c), c) == s] | |
- s.substring(0, n) + s.substring(n, s.length) == s | |
- .red[quickSort(array) == bubbleSort(array)(duplicate?)] | |
- (i :: l).size == (l.size + 1) | |
- ... | |
] | |
??? | |
- b < 0 | |
- overflow | |
- "" | |
- unicode, codec | |
- 因为是random所以不是必现,出现很少的fail,用UT单独记录下来保存 | |
--- | |
.left-column[ | |
## Property based testing | |
] | |
.right-column[ | |
- Focus on the properties of the function -- the "requirements" | |
These properties should be things that are true for any correct implementation. | |
- Different from min, add, sub? | |
* max(a, b) == max(b, a) | |
* max(a, b) > a || max(a, b) > b | |
* max(a, b) >= a && max(a, b) >= b | |
* max(a, b) == a || max(a, b) == b | |
* ... | |
] | |
??? | |
- !(max(1, 1) > 1) | |
- max(a, b) = Int.MaxValue | |
--- | |
.left-column[ | |
## Property based testing | |
] | |
.right-column[ | |
```Java | |
@Test | |
public void testMaxIsBiggerThanBoth() { | |
Random r = new Random(); | |
for (int i = 0; i < 100; i++) { | |
int a = r.nextInt(); | |
int b = r.nextInt(); | |
int max = max(a, b); | |
System.out.printf("a=%d, b=%d, max=%d", a, b, max); | |
assert max >= a && max >= b; | |
} | |
} | |
@Test | |
public void testMaxIsOneOf() { | |
Random r = new Random(); | |
for (int i = 0; i < 100; i++) { | |
int a = r.nextInt(); | |
int b = r.nextInt(); | |
int max = max(a, b); | |
System.out.printf("a=%d, b=%d, max=%d", a, b, max); | |
assert max == a || max == b; | |
} | |
} | |
``` | |
] | |
--- | |
### Refactor code | |
- How to pass code? | |
- Function as value | |
- Single Abstract Method interfaces | |
- Lambda | |
--- | |
### Refactor code | |
```Java | |
interface Function4 { public boolean apply(int a, int b, int max, int max2); } | |
public void testAll(Function4 f) { | |
Random r = new Random(); | |
for (int i = 0; i < 100; i++) { | |
int a = r.nextInt(); | |
int b = r.nextInt(); | |
int max = max(a, b); | |
int max2 = max(b, a); | |
System.out.printf("a=%d, b=%d, max=%d, max2=%d", a, b, max, max2); | |
assert f.apply(a, b, max, max2); | |
} | |
} | |
@Test | |
public void testMaxIsBiggerThanBothWithTestAll() { | |
testAll(new Function4() { | |
@Override | |
public boolean apply(int a, int b, int max, int max2) { | |
return max >= a && max >= b; | |
} | |
}); | |
} | |
@Test | |
public void testMaxIsOneOfWithTestAll() { | |
testAll(new Function4() { | |
@Override | |
public boolean apply(int a, int b, int max, int max2) { | |
return max == a || max == b; | |
} | |
}); | |
} | |
``` | |
--- | |
.left-column[ | |
## Property based testing | |
] | |
.right-column[ | |
### Do it better | |
- only integer | |
- 4 parameters, 2 no need | |
- what's wrong? | |
- 100 randoms? no config | |
- ... | |
] | |
--- | |
.left-column[ | |
## Property based testing | |
] | |
.right-column[ | |
### Framework to rescue | |
* jUnit to UT | |
* Hibernate to ORM | |
* jQuery to js | |
* Taf | |
* ... | |
] | |
--- | |
template: inverse | |
## ScalaCheck | |
--- | |
# Property based testing frameworks | |
- [Haskell: QuickCheck](https://hackage.haskell.org/package/QuickCheck) | |
- [Scala: ScalaCheck](http://scalacheck.org/) | |
- [C++: CppQuickCheck](https://github.com/grogers0/CppQuickCheck) | |
- [Javascript: jsverify](https://github.com/jsverify/jsverify) | |
- [Erlang: proper](https://github.com/manopapad/proper) | |
- [Java: junit-quickcheck](https://github.com/pholser/junit-quickcheck) | |
- [Php: eris](https://github.com/giorgiosironi/eris) | |
- google: XXXLanguage property based testing... | |
--- | |
class: center, middle | |
# ScalaCheck | |
#.red[Generate -> Run(Property) -> Shrink] | |
--- | |
### Gen | |
- Gen.choose | |
```Scala | |
Gen.choose(1, 100) | |
``` | |
- oneOf | |
```Scala | |
Gen.oneOf(1, 3, 6) | |
``` | |
- frequency | |
```Scala | |
Gen.frequency((1, 'a'), (2, 'b')) | |
``` | |
- gen.sample | |
- combination | |
```Scala | |
val myGen2 = for { | |
n <- choose(1, 50) | |
m <- choose(n, 2 * n) | |
} yield (n, m) | |
myGen2.sample | |
``` | |
--- | |
### Gen | |
- custom type | |
```Scala | |
import org.scalacheck.Arbitrary.arbitrary | |
import org.scalacheck.Gen.{oneOf, const} | |
sealed trait Tree | |
case class Node(left: Tree, right: Tree, v: Int) extends Tree | |
case object Leaf extends Tree | |
val genLeaf: Gen[Tree] = const(Leaf) | |
val genNode: Gen[Node] = for { | |
v <- arbitrary[Int] | |
left <- genTree | |
right <- genTree | |
} yield Node(left, right, v) | |
def genTree = oneOf(genLeaf, genNode) | |
genTree.sample | |
``` | |
--- | |
### Gen | |
- statistics | |
* classify | |
```Scala | |
import org.scalacheck.Prop._ | |
def ordered(list: List[Int]) = list == list.sorted | |
val prop1 = forAll { list: List[Int] => | |
classify(ordered(list), "ordered") { | |
classify(list.length > 5, "large", "small") { | |
list.reverse.reverse == list | |
} | |
} | |
} | |
prop1.check | |
``` | |
--- | |
### Property | |
- forAll | |
- p.check | |
```Scala | |
forAll {(a: Int, b: Int) => | |
val max = MathUtil.max(a, b) | |
max >= a || max >= b | |
}.check | |
``` | |
--- | |
### Property | |
- combination | |
* &&, all | |
* ||, atLeastOne | |
* == | |
```Scala | |
val p1 = forAll {(a: Int, b: Int) => | |
val max = MathUtil.max(a, b) | |
max >= a || max >= b | |
} | |
val p2 = forAll {(a: Int, b: Int) => | |
val max = MathUtil.max(a, b) | |
max >= a || max >= b | |
} | |
(p1 && p2).check | |
``` | |
--- | |
### Property | |
- conditional | |
* ==> | |
* condition is hard or impossible to fulfill | |
* pass, fail, undecided | |
```Scala | |
forAll { n: Int => | |
n + 1 > n | |
}.check | |
forAll { n: Int => | |
(n != Int.MaxValue) ==> { | |
n + 1 > n | |
} | |
}.check | |
``` | |
--- | |
### Property Pattern | |
next | |
--- | |
### Shrink | |
- forAll, forAllNoShrink | |
```Scala | |
forAll { list: List[Int] => | |
list.sorted == list | |
}.check | |
forAllNoShrink { list: List[Int] => | |
list.sorted == list | |
}.check | |
``` | |
```Scala | |
implicitly[Shrink[Int]].shrink(100).force | |
implicitly[Shrink[String]].shrink("adfe").force | |
implicitly[Shrink[(String, Int)]].shrink(("adfe", 5)).force | |
``` | |
--- | |
### Config | |
```Scala | |
def myParameter = new Parameters.Default { | |
override val minSuccessfulTests = 200 | |
override val workers = 5 | |
} | |
p1.check(myParameter) | |
``` | |
--- | |
### More | |
- Statefull | |
* Commands | |
- Database | |
- Network | |
- Concurrency | |
- ... | |
--- | |
### Good | |
- More abstract | |
- More general, Test coverage | |
* Radom | |
* Corner case | |
- More concise | |
- Test readability | |
- Maintenance | |
- Test case simplification | |
- .red[**Property-based tests force you to think!**] | |
- .red[**Property-based tests force you to have a clean design!**] | |
--- | |
template: inverse | |
## FAQ | |
--- | |
## Refs | |
- [http://scalacheck.org/documentation.html](http://scalacheck.org/documentation.html) | |
- [http://blog.charleso.org/property-testing-preso/#1](http://blog.charleso.org/property-testing-preso/#1) | |
- [http://fsharpforfunandprofit.com/posts/property-based-testing-2/](http://fsharpforfunandprofit.com/posts/property-based-testing-2/) | |
- [http://www.amazon.com/ScalaCheck-Definitive-Guide-Rickard-Nilsson/dp/0981531695](http://www.amazon.com/ScalaCheck-Definitive-Guide-Rickard-Nilsson/dp/0981531695) | |
- [https://github.com/rickynils/scalacheck/wiki/User-Guide](https://github.com/rickynils/scalacheck/wiki/User-Guide) | |
</textarea> | |
<script src="remark-latest.min.js"></script> | |
<script type="text/javascript"> | |
var hljs = remark.highlighter.engine; | |
</script> | |
<script src="remark.language.js" type="text/javascript"></script> | |
<script type="text/javascript"> | |
var slideshow = remark.create({ | |
highlightStyle: 'monokai', | |
highlightLanguage: 'remark' | |
}) ; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment