What elevates software development into software engineering? In this talk, we’ll make the case that rigorous testing is one of the fundamental ways in which programmers can improve the predictability of the output we produce. We will examine some relatively unknown, or at least not widely adopted, software testing techniques that go beyond basic unit testing to help us deliver complex systems with confidence.
14. GENERIC INVARIANT OR POST
CONDITION OF YOUR CODE, GIVEN
SOME PRECONDITION
“Property-Based Testing in Java”, http://jqwik.net
PROPERTIES
15. DEFINED
PROPERTY-BASED TESTING
▸ It’s still testing
▸ Moves away from programmer-supplied examples
▸ Captures broader invariants ("properties") about programs
▸ Occupies lower part of test pyramid
▸ Why do we care about it?
16. REQUIREMENTS FOR PBT
WHAT DO WE NEED TO IMPLEMENT IT?
▸ Input generation
▸ Assertions about properties that holde
▸ Implies: identification of useful and desirable
properties!
▸ [Optional] Counter-example shrinking
24. class FizzBuzzGenerator extends Generator<List> {
public FizzBuzzGenerator() {
super(List.class);
}
@Override
public List<String> generate(SourceOfRandomness random, GenerationStatus status) {
return FizzBuzzes.fizzBuzz(status.size());
}
@Override
public List<List> doShrink(SourceOfRandomness random, List larger) {
return IntStream.rangeClosed(0, larger.size() - 1)
.mapToObj(maxIdx -> larger.subList(0, maxIdx))
.collect(Collectors.toList());
}
}
25. /**
* Checks that every third element is the literal
* <code>"Fizz"</code>.
*
* @param sequence valid FizzBuzz sequence
*/
@Property
public void fizzes(@From(FizzBuzzGenerator.class)
List<String> sequence) {
for (int i = 3; i <= sequence.size(); i += 3) {
assertThat(sequence.get(i - 1))
.contains(“Fizz");
}
}
28. java.lang.AssertionError: Property primality falsified.
Original failure message: [422886279]
Original args: [422886279]
Args shrunken to: [4]
Seeds: [8922134899668966991]
http://pholser.github.io/junit-quickcheck/
29. THE DEVELOPER IS FORCED TO THINK ABOUT
WHAT THE CODE SHOULD DO AT A HIGH LEVEL
RATHER THAN GRUNT OUT A FEW UNMOTIVATED
TEST CASES.
Joe Nelson, “The Design and Use of QuickCheck”
THINKING ABOUT PROPERTIES
37. class BrokenReporter {
volatile boolean written = false;
void endReport() {
if (written) {
return;
}
written = true; // must only happen once
}
}
38. class BrokenReporter {
volatile boolean written = false;
AtomicInteger numWrites = new AtomicInteger();
void endReport() {
if (written) {
return;
}
written = true; // must only happen once
numWrites.incrementAndGet();
}
}
39. http://openjdk.java.net/projects/code-tools/jcstress/
@JCStressTest
@State
@Outcome(id = "1", expect = Expect.ACCEPTABLE)
class ReporterFixedStressTest {
private Reporter reporter = new Reporter();
private final Object lock = new Object();
@Actor
public void publisher1(IntResult1 result) {
reporter.endReport();
synchronized (lock) {
result.r1 = Math.max(
result.r1,
reporter.numWrites.get());
}
}
// additional actors omitted
}
40. *** FAILED tests
Strong asserts were violated. Correct implementations
should have no assert failures here.
1 matching test results.
[FAILED] ReporterBrokenStressTest
(JVM args: [])
Observed state Occurrences Expectation
1 42,206 ACCEPTABLE
2 174 FORBIDDEN
http://openjdk.java.net/projects/code-tools/jcstress/
41. class FixedReporter {
AtomicBoolean written = new AtomicBoolean(false);
void endReport() {
if (written.compareAndSet(false, true)) {
// happens only once, lock-free
}
}
}
50. gov.nasa.jpf.listener.PreciseRaceDetector
race for field AtomicFail@15b.written
Thread-1 at AtomicFail.onCompletion(AtomicFail.java:23)
"written = true;" WRITE: putfield AtomicFail.written
Thread-2 at AtomicFail.onCompletion(AtomicFail.java:18)
"if (written) {" READ: getfield AtomicFail.written
51. thread java.lang.Thread:{id:0,name:main,status:WAITING,priority:5,isDaemon:false,lockCount:0,suspendCount:0}
waiting on: java.lang.Thread@15c
call stack:
at java.lang.Thread.join(Thread.java)
at AtomicFail.main(AtomicFail.java:13)
thread java.lang.Thread:{id:1,name:Thread-1,status:RUNNING,priority:5,isDaemon:false,lockCount:0,suspendCount:0}
call stack:
at AtomicFail.onCompletion(AtomicFail.java:26)
at AtomicFail$$Lambda$0.run(pc 5)
thread java.lang.Thread:{id:2,name:Thread-2,status:RUNNING,priority:5,isDaemon:false,lockCount:0,suspendCount:0}
call stack:
at AtomicFail.onCompletion(AtomicFail.java:18)
at AtomicFail$$Lambda$0.run(pc 5)