Diamond Application Development Crafting Solutions with Precision
White-box Unit Test Generation with Microsoft IntelliTest
1. Budapest University of Technology and Economics
Department of Measurement and Information Systems
Budapest University of Technology and Economics
Fault Tolerant Systems Research Group
White-box Unit Test Generation
with Microsoft IntelliTest
Dávid Honfi
1
3. White-box test design
public int M1(int a, int b)
{
if(a == 0)
{
Console.WriteLine(ERROR_MSG);
return -1;
}
if(b > a) return b*a+5;
else return (a+b)/2;
}
Let’s cover all
the statements!
a==0
a!=0 && b>a
a!=0 && b<=a
a==0 a!=0 && b<=a a!=0 & b>a
a 0 1 2
b 0 0 3
4. What’s missing?
Usually encoding observed outputs
Where can it be employed?
oBasic, common bugs (e.g., uncaught exceptions)
oViolation of assertions or contracts
oDeviations from existing outputs (regression)
Automation with code exploration
test case = input + expected output
5. Symbolic execution: the idea
Static program analysis technique from the ’70s
Application for test generation
o Symbolic variables instead of normal ones
o Constraints forming for each path with symb. variables
o Constraint solving (e.g., SMT solver)
o A solution yields an input to execute a given path
New century, new progress:
o Enough computing power (e.g., for SMT solvers)
o New ideas, extensions, algorithms and tools
5
6. Existing tools using symbolic execution
.NET: Microsoft IntelliTest (a.k.a. Pex)
x86 binary: Microsoft SAGE
Java
oNASA Symbolic PathFinder
oCATG
o…
JavaScript: Jalangi
C: KLEE
9. public int M1(int a, int b)
{
if(a == 0)
{
Console.WriteLine(ERROR_MSG);
return -1;
}
if(b > a) return b*a+5;
else return (a+b)/2;
}
Details of IntelliTest (Dynamic SE)
Source code
Concrete
input values
Symbolic
variables
Constraint
solver
Constraints
Concrete
execution
Symbolic
execution
a==0
a:=0
b:=0 a,b
Transformationa!=0
a:=1
b:=0
10. public int M1(int a, int b)
{
if(a == 0)
{
Console.WriteLine(ERROR_MSG);
return -1;
}
if(b > a) return b*a+5;
else return (a+b)/2;
}
Details of IntelliTest (Dynamic SE)
Source code
Concrete
input values
Symbolic
variables
Constraint
solver
Constraints
Concrete
execution
Symbolic
execution
a!=0 &&
b<=a
a!=0 && b>a
a:=1
b:=0
a:=2
b:=3
11. Parameterized Unit Testing
Idea: Using test methods as specifications
o Easy to understand, easy to check, etc.
o But: too specific (used for a code unit), verbose, etc.
Parameterized Unit Test (PUT)
o Wrapper method for method/unit under test
o Main elements
• Inputs of the unit
• Assumptions for input space restriction
• Call to the unit
• Assertions for expected results
o Serves as a specification Test generators can use it
11
12. Example: Parameterized Unit Testing
12
void ReduceQuantityPUT(Product prod, int soldCount) {
// Assumptions
Assume.IsTrue(prod != null && prod.Quantity > 0);
Assume.IsTrue(soldCount > 0);
int oldQuantity = prod.Quantity;
// Calling the UUT
int newQuantity = StorageManager.ReduceQuantity(prod,soldCount);
// Assertions
Assert.IsTrue(newQuantity >= 0);
Assert.IsTrue(newQuantity < oldQuantity);
}
/// The method reduces the quantity of the specified
/// product. The product is known to be NOT null with
/// quantity larger than 0, also the sold amount is
/// always more than zero. The method has effects on
/// the database, and returns the new quantity of the
/// product. If the quantity would be negative, the
/// method reduces the quantity to zero.
int ReduceQuantity(Product prod, int soldCount) { … }
13. Example: Parameterized Unit Testing
13
void ReduceQuantityPUT(Product prod, int soldCount) {
// Assumptions
Assume.IsTrue(prod != null && prod.Quantity > 0);
Assume.IsTrue(soldCount > 0);
int oldQuantity = prod.Quantity;
// Calling the UUT
int newQuantity = StorageManager.ReduceQuantity(prod,soldCount);
// Assertions
Assert.IsTrue(newQuantity >= 0);
Assert.IsTrue(newQuantity < oldQuantity);
}
/// The method reduces the quantity of the specified
/// product. The product is known to be NOT null with
/// quantity larger than 0, also the sold amount is
/// always more than zero. The method has effects on
/// the database, and returns the new quantity of the
/// product. If the quantity would be negative, the
/// method reduces the quantity to zero.
int ReduceQuantity(Product prod, int soldCount) { … }
15. Challenges of SE-based techniques
1. Exponential growth of execution paths
2. Complex arithmetic expressions
3. Floating point operations
4. Compound structures and objects
5. Pointer operations
6. Interaction with the environment
7. Multithreading
8. …
15
T. Chen et al. „State of the art: Dynamic symbolic execution for automated test generation”.
Future Generation Computer Systems, 29(7), 2013
16. Challenges of SE-based techniques
1. Exponential growth of execution paths
2. Complex arithmetic expressions
3. Floating point operations
4. Compound structures and objects
5. Pointer operations
6. Interaction with the environment
7. Multithreading
8. …
16
T. Chen et al. „State of the art: Dynamic symbolic execution for automated test generation”.
Future Generation Computer Systems, 29(7), 2013
SEViz
17. Visualizing symbolic execution
SEViz: Symbolic Execution VIsualiZer
Goal: Help identifying test generation problems
o Long execution paths
o Unsolvable constraints
o …
Solution: Symbolic execution tree with metadata
17
Mapping to
source code
Calls to constraint solver
1
No Yes
End of execution paths
No test Failed test Passed test
1 11 1 1 1
SHAPE BORDER COLOR
18. Visualizing symbolic execution
Additional metadata
o Sequence number
o Path condition
o Incremental path condition (based on parent)
o Status: can have siblings or not
o Source code mapping (if available)
18
20. Challenges of SE-based techniques
1. Exponential growth of execution paths
2. Complex arithmetic expressions
3. Floating point operations
4. Compound structures and objects
5. Pointer operations
6. Interaction with the environment
7. Multithreading
8. …
20
T. Chen et al. „State of the art: Dynamic symbolic execution for automated test generation”.
Future Generation Computer Systems, 29(7), 2013
SEViz
Automated
isolation
21. Existing solutions
Stubbing and mocking (faking)
o Fixed values and checks for all DSE executions
o Not suitable for test generation
Parameterized mocking
o Interaction with DSE is possible
• More relevant test cases
• Custom behavior in mocks: e.g., state change of objects
o Introduces complexity for users of DSE
• Requires large amount of time and effort
• Not trivial task in case of complex structures
o Fakes cannot be generated under certain conditions
21
22. Approach for automated isolation
Automated isolation on source code level
1. Abstract syntax tree transformations in the SUT
2. Parameterized sandbox synthesization
22
SUT
C1
C2
Environment
i(C1,C2)
i(C2,E)
i(C2,C1)
i(C1,E)
SUT
C1’ C2’
Environment
SB i'(C2,E)i'(C1,E)
i(C2,C1)
i(C1,C2)
Replaced
external
objects
Replaced
external
calls
Sandbox
23. Example of AST transformation
23
public class WeekendNotifier {
public bool IsWeekendNear() {
DateTime date = DateTime.GetNow();
date.AddDays(2);
if(date.GetDay() == "Saturday") return true;
return false;
}
}
public class WeekendNotifier {
public bool IsWeekendNear() {
DynamicFake date = Fake.DateTimeGetNow();
Fake.DateTimeAddDays(2,date);
if(Fake.DateTimeGetDay(date) == "Saturday") return true;
return false;
}
}
24. Example of parameterized sandbox
24
public static class Fake {
public DynamicFake DateTimeGetNow() {
// Return a state container object instead of the original
return new DynamicFake();
}
public void DateTimeAddDays(int days, DynamicFake date) {
// TODO: State change of date using DSE
}
public int DateTimeGetDay(DynamicFake date) {
// Obtaining return value from DSE
return DSEEngine.ChooseValue<int>();
}
}
25. Summary
White-box test generation: symbolic execution
IntelliTest, a state-of-the-art test generator
o Dynamic Symbolic Execution (DSE)
o Parameterized Unit Tests
DSE has several challenges, e.g.:
o Test generation problem indentification
o Environment dependencies
My research
o SEViz: A tool for visualizing symbolic execution
o Automated isolation with source code transformation
25
De hogyan is néz ki a forráskód alapú teszttervezés
A cél természetesen specifikáció hiányában például az lehet, hogy fedjük az összes lehetséges utasítást.
Hát csináljuk is ezt meg kézzel. Van egy writelineunk és utána egy returnünk. A bemeneti paramétereket figyelembe véve milyen feltétellel juthatunk el ezekre az utasításokra?
Nyilvánvalóan ha a==0, de nézzük a következő return utasítást. Ide milyen feltételt szabhatunk a bemeneti változóinkra? Azt tudjuk már, hogy az a!=0, és most hozzá ÉS-eljük a b>a-t.
Hasonlóképp a harmadik return utasításhoz is szabhatunk feltételt, ha az a!=0-t összeÉS-eljük a b<=a-val.
Oké, de ebből hogyan kapunk teszteket? Hát nagyon egyszerű módon: fogjuk a formulákat és kielégítő behelyettesítéseket adunk a változóiknak, ahogy például a táblázatban szerepel.
Ezekkel a bemenetekkel már pont meghajthatjuk a formulákhoz tartozó egyes utasításokat, így tesztelhetjük őket.
Na jó, de azért egy kicsit valami sántít. Mi maradt ki?
Hát az, hogy egy teszteset az nem csak bemenetekből, hanem elvárt kimenetekből és még egyéb tesztadatokból is állhat.
De mégis mi az, amit elvárt kimenet nélkül észre tudunk venni?
Egyszerű, általános hibákat úgy mint nem várt kivételek
Észrevehetjük a kódban lévő assertionök megsértését
Esetleg regressziós tesztkészletként használhatjuk és az újabb forráskódverziók kimeneteit összevethetjük a korábbiakkéval.
Esetleg több különböző implementáció ekvivalenciájának ellenőrzését is megoldhatjuk vele.
De lássuk, hogy milyen eszközök léteznek a kód alapú tesztgenerálásra
.NET-hez van két Microsoft Research fejlesztés
Az egyik, amiről ma is beszélek majd: az IntelliTest (korábbi nevén Pex)
Míg a másik a SAGE, ami egy belső toolja a Microsoftnak security testingre. Elmondásaik alapján mindennap használják a Windows és az Office fejlesztéséhez A Win7 security bugjainak nagy részét ezzel találták meg.
Természetesen Java-hoz is léteznek eszközök, az egyiket ráadásul a NASA fejleszti Symbolic PathFinder néven.
Említhetjük még a JavaScript-es Jalangit és a C-s KLEE eszközt is. A KLEE érdekessége, hogy belső engine-ként felhasználja a Cloud9 tesztgeneráló eszköz, amit ha jól tudom a svájci részecskekutató-intézetben a CERN-ben is használnak.
Az azért látható a fejlesztőkből, hogy olyan szervezetek is használják őket, ahol biztonságkritikus fejlesztések folynak.
Az említett eszközök közül én ma az IntelliTest-ről fogok nektek bővebben mesélni.
Az IntelliTest a Visual Studio 2015 Enterprise verziójában jelent meg először ezen a néven
Néhányotok számára viszont ismerős lehet a Pex vagy a Code Digger elnevezés is.
A Pex az IntelliTest korábbi neve volt, most pedig az eszköz mögött lévő engine-t nevezik így.
A Code Digger pedig a VS2013-hoz volt elérhető kiegészőként, ez a Pex egy jelentősen lebutított változata volt.
- Menjünk bele egy kicsit a részletekbe is, és lássuk mi is az a szimbolikus végrehajtás.
- A Pex engine a háttérben hasonlóképp működik ahogyan azt a dián kézzel is végigvittük. A kód bejárását alacsony szintű .NET-es mágiával és saját parancsértelmezővel viszi véghez.
- A bejáráshoz konkrét végrehajtást alkalmaz, amely során összegyűjti azokat a feltételeket avagy formulákat, amik az egyes, még felderítetlen kódrészek bejárásához szükségesek (ahogyan ezt mi is kézzel megtettük).
- Ezeket átadja egy speciális eszköznek (úgynevezett Z3 kényszermegoldónak - szintén MS Research fejlesztés), amitől visszakapja ezeknek a formuláknak egy konkrét behelyettesítését.
Ez a behelyettesítés fogja adni egy adott végrehajtási útvonalhoz szükséges bemeneteket.
És itt jutottunk egy visszacsatoláshoz, a Pex addig fogja ezt az iterációt csinálni, amíg létezik olyan lefutási útvonal, aminek a kényszerét még nem oldotta meg.
- Mind mondottam, az IntelliTestnek ez csak a legegyszerűbb használati esete volt.
- Lássuk, hogy egy kicsivel bonyolultabb problémát hogyan old meg az eszköz.
Of course, there are existing solutions to alleviate the environment dependency problem: such as Stubbing and Mocking
However, this uses fixed values and checks for all DSE execution paths, and thus not suitable for test generation
That is the reason why a technique called parameterized mocking was introduced recently, that enables the interaction of DSE and mocks
By using this technique DSE can generate more relevant cases, and also it enables the definition of custom behavior like changing states of objects in the mocks by using values obtained from the DSE
On the other, this introduces, complexity… AUTOMATION
Also, the fake objects are usually provided by isolatino frameworks that cannot be used under certain conditions and have several limitations AVOID USING ISOLATION FWs
Thus our approach is to automatically isolate the dependencies on the source code level in order to alleviate DSE-based test generation
* First, our technique transforms the AST of the unit under test, then an algorithm synthesizes a parameterized sandbox that can be utilized by the DSE itself
* In order to show it visually, consider the previous example, our approach transforms the unit under test by replacing external type usages and external invocations
* Also, the tecnhnique generates a paramterized sandbox, where the replace invocations are pointing to
* And in the end, you can see that there is no access to the environment
External objects ->DynamicFake state containers
On this slide, I would like to present the AST transformation by an example class called WeekendNotifier
…
So, we have just transformed the unit under test, however our invocations are pointing to nowhere. At this point, the generation of the sandbox comes in.
The external invocations are synthesized in a form of method stubs into a Fake class that contains all of these synthesized methods..
F