Mutation Testing Meets Approximate Computing

Slides:



Advertisements
Similar presentations
MUTATION TESTING. Mutation testing is a technique that focuses on measuring the adequacy (quality) of test data (or test cases). Modify a program by introducing.
Advertisements

Object Oriented Programming COP3330 / CGS5409.  C++ Automatics ◦ Copy constructor () ◦ Assignment operator =  Shallow copy vs. Deep copy  DMA Review.
Chapter 1. The Phases of Software Development. Data Structure 2 Chapter outline  Objectives  Use Javadoc to write a method’s complete specification.
(c) 2007 Mauro Pezzè & Michal Young Ch 16, slide 1 Fault-Based Testing.
Copyright © 2006 The McGraw-Hill Companies, Inc. Programming Languages 2nd edition Tucker and Noonan Chapter 18 Program Correctness To treat programming.
Michael Ernst, page 1 Improving Test Suites via Operational Abstraction Michael Ernst MIT Lab for Computer Science Joint.
MS thesis/project ideas for MS students in the Department of Computer Science at the University of Minnesota Duluth suggestions by Dr Andrew Brooks, Heller.
State coverage: an empirical analysis based on a user study Dries Vanoverberghe, Emma Eyckmans, and Frank Piessens.
TASK ADAPTATION IN REAL-TIME & EMBEDDED SYSTEMS FOR ENERGY & RELIABILITY TRADEOFFS Sathish Gopalakrishnan Department of Electrical & Computer Engineering.
Comp 248 Introduction to Programming Chapter 4 - Defining Classes Part A Dr. Aiman Hanna Department of Computer Science & Software Engineering Concordia.
Topic #10: Optimization EE 456 – Compiling Techniques Prof. Carl Sable Fall 2003.
Software Testing and Validation SWE 434
© SERG Dependable Software Systems (Mutation) Dependable Software Systems Topics in Mutation Testing and Program Perturbation Material drawn from [Offutt.
Using Loop Perforation to Dynamically Adapt Application Behavior to Meet Real-Time Deadlines Henry Hoffmann, Sasa Misailovic, Stelios Sidiroglou, Anant.
Regression Testing. 2  So far  Unit testing  System testing  Test coverage  All of these are about the first round of testing  Testing is performed.
Bug Localization with Machine Learning Techniques Wujie Zheng
Software testing techniques Software testing techniques Mutation testing Presentation on the seminar Kaunas University of Technology.
Testing Testing Techniques to Design Tests. Testing:Example Problem: Find a mode and its frequency given an ordered list (array) of with one or more integer.
CPSC 388 – Compiler Design and Construction Optimization.
BEGINNING PROGRAMMING.  Literally – giving instructions to a computer so that it does what you want  Practically – using a programming language (such.
CSCI1600: Embedded and Real Time Software Lecture 28: Verification I Steven Reiss, Fall 2015.
Mutation Testing Breaking the application to test it.
MUTACINIS TESTAVIMAS Benediktas Knispelis, IFM-2/2 Mutation testing.
Detecting Assumptions on Deterministic Implementations of Non-deterministic Specifications August Shi, Alex Gyori, Owolabi Legunsen, Darko Marinov 4/12/2016.
Mutation Testing Laraib Zahid & Mariam Arshad. What is Mutation Testing?  Fault-based Testing: directed towards “typical” faults that could occur in.
Test Case Purification for Improving Fault Localization presented by Taehoon Kwak SoftWare Testing & Verification Group Jifeng Xuan, Martin Monperrus [FSE’14]
Complexity Analysis (Part I)
© A+ Computer Science - Magpie Chatbot Lab © A+ Computer Science -
By Santiago Anllo and Agustín Rizzolo
May 17th – Comparison Sorts
Repetition Structures Chapter 9
CS1101X Programming Methodology
FUNCTIONS In C++.
Simple Sorting Algorithms
Source: wikipedia
Mutation testing Julius Purvinis IFM-0/2.
Cse 373 May 15th – Iterators.
It is great that we automate our tests, but why are they so bad?
Balancing Trade-Offs in Test-Suite Reduction
Graph Paper Programming
Alex Groce, Josie Holmes, Darko Marinov, August Shi, Lingming Zhang
August Shi, Tifany Yung, Alex Gyori, and Darko Marinov
Solution of Equations by Iteration
Introduction to Data Structures
What is CS 253 about? Contrary to the wide spread belief that the #1 job of computers is to perform calculations (which is why the are called “computers”),
Test Case Purification for Improving Fault Localization
Programming Languages 2nd edition Tucker and Noonan
Java Programming Loops
Analyzing an Algorithm Computing the Order of Magnitude Big O Notation
Explaining issues with DCremoval( )
Mutation Testing The Mutants are Coming! Copyright © 2017 – Curt Hill.
Simple Sorting Algorithms
Java Programming Loops
Algorithms: Design and Analysis
Measuring Experimental Performance
Assertions References: internet notes; Bertrand Meyer, Object-Oriented Software Construction; 4/25/2019.
Collision Handling Collisions occur when different elements are mapped to the same cell.
point when a program element is bound to a characteristic or property
Simple Sorting Algorithms
Working with the Compute Block
Programming Languages 2nd edition Tucker and Noonan
Complexity Analysis (Part I)
Junit Tests.
Algorithms and data structures: basic definitions
Complexity Analysis (Part I)
Software Testing and QA Theory and Practice (Chapter 5: Data Flow Testing) © Naik & Tripathy 1 Software Testing and Quality Assurance Theory and Practice.
Mitigating the Effects of Flaky Tests on Mutation Testing
Introduction to Methods and Interfaces
Mutation Testing Faults are introduced into the program by creating many versions of the program called mutants. Each mutant contains a single fault. Test.
Presentation transcript:

Mutation Testing Meets Approximate Computing Milos Gligoric1, Sarfraz Khurshid1, Sasa Misailovic2, August Shi2 ICSE NIER 2017 Buenos Aires, Argentina May 24, 2017 “My name is August Shi. I am from the University of Illinois at Urbana-Champaign. I will be presenting our work Mutation Testing Meets Approximate Computing. This is joint work with Milos and Sarfraz, who are from the University of Texas at Austin, and with Sasa, who is also from the University of Illinois at Urbana-Champaign. Our work involves both mutation testing and approximate computing, so I will start with a bit of background on both.” 1 2 CCF-1409423, CCF-1421503, CCF-1566363, CCF-1629431, CCF-1319688, CNS-1239498

Mutation Testing Goal: Evaluate quality of test suites How: Apply transformations (mutation operators) on the code and run tests to see if they can detect the changes Example: x = x + 1 “Mutation testing is a technique used for many years, particularly in software testing research, to evaluate quality of test suites” Say transformations = mutation operators

Mutation Testing Goal: Evaluate quality of test suites How: Apply transformations (mutation operators) on the code and run tests to see if they can detect the changes Example: x = x + 2 Problems: Evaluation of quality limited by mutation operators Too slow “Mutation testing is a traditional technique used in research to evaluate quality of test suites” Say transformations = mutation operators

Approximate Computing Goal: Improve performance of code How: Apply transformations that may lead to (slightly) inaccurate results Example: for (i = 0; i < n; i = i + 1) “Approximate computing is a new, emerging area of research, especially in the programming systems community, that does something similar, but has a different goal” Point out how transforming code is a common factor in the “how” part between the two

Approximate Computing Goal: Improve performance of code How: Apply transformations that may lead to (slightly) inaccurate results Example: for (i = 0; i < n; i = i + 2) Problems: Not sure where in exact code to apply approximations Unclear how to check quality of tests on already approximate code “Approximate computing is a new, up-and-coming area of research that does something similar, but has a different goal” Goal of performance, e.g., speed (tie in with later part) Point out how transforming code is a common factor in the “how” part between the two

How can Mutation Testing and Approximate Computing improve one another? “More specifically, can we use one to mitigate the problems of the other?”

Improving One Another Approximate computing to provide new mutation operators for evaluating quality of tests Approximate computing to improve speed of mutation testing Mutation testing to point out opportunities for applying approximations on exact code Mutation testing to evaluate quality of tests on (already) approximate code

Example Code: Commons-Math // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i++) { 0 values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Passes “I would like to demonstrate some of these ideas using an example…”

Mutant: Constant Replacement // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i++) { values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 1; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Fails Mutant Killed Replace 0 with 1

Mutant: Constant Replacement // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 1; i < data.length; i++) { 0 values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Passes Mutant Survived Replace 0 with 1

Approximate: Loop Perforation // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i+=2) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Fails Normally, this transformation would cause the test to fail In such a case, the approximation is invalid. So a developer has two options: the developer can do a different approximation, or if the developer has determined the approximation is good and it’s the test that’s too rigid, the developer can modify the test to accept the approximation Skip every other iteration 68% of runtime is in this loop

Approximate: Loop Perforation Modify assertion Approximate: Loop Perforation // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i+=2) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArraySubset(values, MathArrays.unique(x), 0); } Test Passes For the sake of our example, let’s say the developer chooses to modify the test assertion to allow for this approximation Let’s say there is this new assertion that checks if elements are a subset of another (though assertion does not actually exist) Because in approximate computing, we need to have the test pass Approximation is Acceptable Skip every other iteration 68% of runtime is in this loop

Comparison of Transformation Results Mutation Testing Approximate Computing Failing Test While we see that both mutation testing and approximate computing do transformations, they expect different things from the results of tests due to transformations There seems to be a conflict between expectations of if the test passes or fails after transformations based on which mindset a developer is in Passing Test

Comparison of Transformation Results Mutation Testing Approximate Computing Failing Test When we start considering how mutation testing and approximate computing can improve one another, this contrast in expectations is not so clear cut Passing Test

Approx. Transformation as Operator // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i++) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } One of our ideas for approximate computing to help mutation testing…

Approx. Transformation as Operator // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i+=2) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Fails Mutant Killed Replace i++ with i+=2 Perforates loop

Questions for Approx. Operators Do killed approximate mutants indicate different strengths? Do surviving approximate mutants indicate new weaknesses in the test suite? How do mutants generated by approximate computing differ from traditional mutants? Are approximate mutants faster than traditional mutants? What are characteristics of mutants generated by approximate transformations for mutation testing?

Mutants Find Approx. Opportunities // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 0; i < data.length; i++) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); }

Mutants Find Approx. Opportunities // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 1; i < data.length; i++) values.add(data[i]); } int count = values.size(); double[] out = new double[count]; Iterator<Double> iterator = values.descendingIterator(); int i = 0; while (iterator.hasNext()) { out[i++] = iterator.next(); return out; // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2}; double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Passes For example, the 0 is redundant in the first location, so perhaps there is some leeway for approximation here Approximable? Replace 0 with 1

Questions for Approx. Opportunities How can we classify surviving mutants? Are they good for approximate computing? What approximations are applicable for which surviving mutants? How can we tailor mutants for the purpose of finding approximate computing opportunities? Tailored mutants have benefit of being automatic, to test the waters before more effort for actual, better approximation

Improving One Another More in paper! Approximate computing to provide new mutation operators for evaluating quality of tests Approximate computing to improve speed of mutation testing Mutation testing to point out opportunities for applying approximations on exact code Mutation testing to evaluate quality of tests on (already) approximate code More in paper!

Conclusions Approximate computing can provide new mutation operators Mutation testing can show opportunities for approximate computing on exact code There is so much more we can do (More directions in the paper) Many more directions for exploring the combination of mutation testing and approximate computing to improve one another August Shi: awshi2@illinois.edu

BACKUP

Mutating Approximate Code? // MathArrays.java static double[] unique(double[] data) { TreeSet<Double> values = new TreeSet<>(); for (int i = 1; i < data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } return values.toArray(); // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2} double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test Passes Replace 0 with 1

Mutation testing approximate code? Approximate Transformation Instance % Mutants Killed Exact Version N/A 95 Loop Perforation (Line 04) Skip every 2nd iteration 100 Skip every 4th iteration 95 Only execute every 4th iteration If approximate version is proxy of exact version, is mutation score of approximate version also proxy of mutation score of exact version? If so, what are the certain conditions where they are good proxies? What precisely do these changes in percentages mean?

Directions for Research If approximate version is proxy of exact version, is mutation score of approximate version also proxy of mutation score of exact version? Do I get same confidence in quality of tests at cheaper cost? If so, what are the exact conditions where they are good proxies?

Approximate Code to Speed up Testing? // MathArrays.java 01. static double[] unique(double[] data) { 02. TreeSet values = 03. new TreeSet<>(); 04. for (int i = 0; 05. i < data.length; i++) { 06. if (i%2 != 0) continue; 07. values.add(data[i]); 08. } 09. int count = values.size() 10. double[] out = new double[count]; 11. Iterator iterator = 12. values.descendingIterator(); 13. int i = 0; 14. while (iterator.hasNext()) { 15. if (i == data.length / 2 + 1) break; 16. out[i++] = iterator.next(); 17. } 19. return out; 20. } // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2} double[] values = {11, 3, 0, -1}; assertArrayEquals(values, MathArrays.unique(x), 0); } Loop (line 04) takes 68% of runtime Perforation can cut time in half

Approximate Code to Speed up Testing? // MathArrays.java 01. static double[] unique(double[] data) { 02. TreeSet values = 03. new TreeSet<>(); 04. for (int i = 0; 05. i < data.length; i++) { 06. if (i%2 != 0) continue; 07. values.add(data[i]); 08. } 09. int count = values.size() 10. double[] out = new double[count]; 11. Iterator iterator = 12. values.descendingIterator(); 13. int i = 0; 14. while (iterator.hasNext()) { 15. if (i == data.length / 2 + 1) break; 16. out[i++] = iterator.next(); 17. } 19. return out; 20. } // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2} double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertApproxEquals(values, MathArrays.unique(x), 0 0.8); } Introduce new assertions?

Example: Mutation Testing (SURVIVED) // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 0; i < data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 1; i < data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; Constant Replacement Original Mutant // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2} double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test passes on original Test passes on mutant => Mutant SURVIVED

Example: Mutation Testing (KILLED) // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 0; i < data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 0; i <= data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; Boundary Mutator Original Mutant // MathArraysTest.java void testUnique() { double[] x = {0, 9, 3, 0, 11, 7, 3, 5, −1, −2} double[] values = {11, 9, 7, 5, 3, 0, -1, -2}; assertArrayEquals(values, MathArrays.unique(x), 0); } Test passes on original Test passes on mutant => Mutant KILLED

Example: Loop Perforation // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 0; i < data.length; i++) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; // MathArrays.java static double[] unique(double[] data) { TreeSet values = new TreeSet<>(); for (int i = 0; i < data.length; i+=2) { if (i%2 != 0) continue; values.add(data[i]); } int count = values.size() double[] out = new double[count]; … return out; Skip Every Other Iteration Original Mutant