Showing posts with label Pex. Show all posts
Showing posts with label Pex. Show all posts

Wednesday, 20 August 2008

Pex - Test Case 7

Hello,

In this Pex test case I will investigate unit test on classes the have dependencies.( feel free to read the previous posts regarding this subject).
I'll first start with how you would unit test this scenario without the help of Pex and afterwards I'll investigate how Pex might help.

I have requirement to calculate the bonus a salesperson earns at the end of the year. The bonus depends on the sales he/she made this year and the year before.

The rules are

  • The bonus amounts to 1% of the sales for that person

  • If this year's sales is higher than 30000 € AND ALSO this year's sales are higher than the Sales of the previuos year for that salesperson, an extra bonus is added of 250 €



A quick test analysis should result in following test cases

















currentYearSales greater or equal than 30000 € YesYesNoNo
currentYearSales greater than LastYearSales YesNoYesNo
Base bonus (1% currentYearSales )XXXX
Extra bonus (250€)X---


So three test cases would be sufficient for complete test coverage (last two test cases can be collapsed).

Suppose you implements the rule in a bonus class and to know the sales amounts you have a Sales class that has appropriate methods to give you them. You've also encapsulated the retrival of these sales amounts inside the bonus class method that calculates the bonus.


public class Bonus
{
public double CalculateForEmployee(uint EmployeeId)
{
double baseBonusPercentage = 1;
double extraBonusAmount = 250;
uint minSalesForBonus = 30000;

Sales sales = new Sales();
uint currentYearSales = sales.CurrentYearSalesForEmployee(EmployeeId);
uint lastYearSales = sales.LastYearSalesForEmployee(EmployeeId);

double bonus = currentYearSales * (baseBonusPercentage / 100);

if (currentYearSales >= minSalesForBonus && currentYearSales > lastYearSales)
bonus += extraBonusAmount;

return System.Math.Floor(bonus);
}
}



public class Sales
{
public uint LastYearSalesForEmployee(uint EmployeeID)
{
//Could be a database call
}

public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
//Could be a database call
}
}


Suppose you want to unit-test the method CalculateForEmployee in the Bonus class. The method calls into a Sales object to retrieve the sales for this year and the year before. Our code-under-test depends on another object to do its job. Nothing prevents you to write a standard unit test calling CalculateForEmployee.


[TestMethod()]
public void CalculateForEmployeeTest()
{
Bonus target = new Bonus();
uint EmployeeId = 0; // ?
double expected = 0; // ?
double actual;
actual = target.CalculateForEmployee(EmployeeId);
Assert.AreEqual(expected, actual);

}


But how do I know which EmployeeId to use and how do I know what the expected value for the bonus should be? In fact the method-under-test has dependencies we don't control in the unit test. The bonus class doesn't control the value of the sales. it's the responsibility of the sales class. As a matter a fact this situation isn't a pure unit test anymore. It's a unit integration test. So how can we control this dependency on the sales object and know in advance the EmployeeId and Sales amounts necessary for our method-under-test. Suppose a database is used to store sales information. We could setup a test database with all the necassary records for retrieving the sales for a particular sales employee. But this would still not classify our test as a unit test (only testing the rules for caculating the bonus). Also you must account for setting up three different test situation in the database.

Rewritting our classes a little bit could helps us. Interfaces, dependency injection and test doubles are techniques I will us to make the CalculateForEmployeeTest easier to test : knowing it advance what the sales amounts will be in order to assert the bonus.

First we extract an interface from our Sales class

public interface ISales
{
uint LastYearSalesForEmployee(uint EmployeeID);
uint CurrentYearSalesForEmployee(uint EmployeeID);
}




public class Sales : ISales
{
public uint LastYearSalesForEmployee(uint EmployeeID)
{
//Could be a database call
}
public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
//Could be a database call
}
}


In this example I'll use "manual" dependency injection (constructor injection) to demostrate the dependency injection principle.


public class Bonus
{
ISales mSalesdao;


public Bonus(ISales aSalesDao)
{
mSalesdao = aSalesDao;
}

public double CalculateForEmployee(uint EmployeeID)
{
double baseBonusPercentage = 1;
double extraBonusAmount = 250;
uint minSalesForBonus = 30000;
double bonus;

uint currentYearSales = mSalesdao.CurrentYearSalesForEmployee(EmployeeID);
uint lastYearSales = mSalesdao.LastYearSalesForEmployee(EmployeeID);

bonus = currentYearSales * (baseBonusPercentage / 100);
if (currentYearSales >= minSalesForBonus && currentYearSales > lastYearSales)
bonus += extraBonusAmount;

return System.Math.Floor(bonus);
}
}


So how does this help me in my unit test you might wonder? A test-double is a test-specific class that we will use in our unit tests so the code-under-test will not notice it that much. There are several variations of test-doubles. For example a test stub is an equivalent of the dependend class but gives "fixed answers" if we ask for something or does something in a pre-determined way.

So instead of passing a real instance of the type Sales to the constructor of Bonus, I slip in a test stub when I write the unit tests. Because it gives fixed answers , I'll need a test stub for each test case.


public class SalesStub_CYSsmaller30000 : ISales
{

public uint LastYearSalesForEmployee(uint EmployeeID)
{
return 29999;
}
public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
return 29998;
}
}

public class SalesStub_CYSgreater30000_CYSgreaterLYS : ISales
{
public uint LastYearSalesForEmployee(uint EmployeeID)
{
return 30001;
}
public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
return 30002;
}

}

public class SalesStub_CYSgreater30000_CYSsmallerLYS : ISales
{

public uint LastYearSalesForEmployee(uint EmployeeID)
{
return 30003;
}

public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
return 30002;
}

}

The unit tests use these stubs to do their thing. I create a teststub that is appropriate for the test case and inject it into the constructor of bonus.


[TestMethod()]
public void CalculateForEmployee_Lys29999_cs29998_withStub()
{
ISales aSalesDao = new SalesStub_CYSsmaller30000();
Bonus target = new Bonus(aSalesDao);
uint EmployeeID = 0; // Dummy EmployeeID not important for this example
double expected = 299; //
double actual;
actual = target.CalculateForEmployee(EmployeeID);
Assert.AreEqual(expected, actual);
}


[TestMethod()]
public void CalculateForEmployee_Lys30001_cs30002_WithStub()
{
ISales aSalesDao = new SalesStub_CYSgreater30000_CYSgreaterLYS();
Bonus target = new Bonus(aSalesDao);
uint EmployeeID = 0; // Dummy EmployeeID not important for this example
double expected = 550; //
double actual;
actual = target.CalculateForEmployee(EmployeeID);
Assert.AreEqual(expected, actual);
}

//and so


Another implementation for test doubles is the use of a Mock framework. This utility dynamically create test-equivalent objects that are setup (expectations) through the framework facilities . I'll illustrate this with the help of Rhino.Mock.


[TestMethod()]
public void CalculateForEmployee_Lys29999_cs29998_withMock()
{
uint EmployeeID = 0; // Dummy EmployeeID not important for this test purpose

Rhino.Mocks.MockRepository mr = new Rhino.Mocks.MockRepository();
ISales salesDao = mr.CreateMock();
Rhino.Mocks.Expect.Call(salesDao.LastYearSalesForEmployee(EmployeeID)).Return(29999);
Rhino.Mocks.Expect.Call(salesDao.CurrentYearSalesForEmployee(EmployeeID)).Return(29998);
mr.ReplayAll();

Bonus target = new Bonus(salesDao);


double expected = 299;
double actual;
actual = target.CalculateForEmployee(EmployeeID);
Assert.AreEqual(expected, actual);

mr.VerifyAll();


}


[TestMethod()]
public void CalculateForEmployee_Lys30001_cs30002_WithMock()
{
uint EmployeeID = 0; // Dummy EmployeeID not important for this test purpose

Rhino.Mocks.MockRepository mr = new Rhino.Mocks.MockRepository();
ISales salesDao = mr.CreateMock();
Rhino.Mocks.Expect.Call(salesDao.LastYearSalesForEmployee(EmployeeID)).Return(30001);
Rhino.Mocks.Expect.Call(salesDao.CurrentYearSalesForEmployee(EmployeeID)).Return(30002);
mr.ReplayAll();

Bonus target = new Bonus(salesDao);


double expected = 550; //
double actual;
actual = target.CalculateForEmployee(EmployeeID);
Assert.AreEqual(expected, actual);

mr.VerifyAll();
}
//and so on


The use of a Mock framework doesn't force me to explicitly write numerous of test stubs.

So where does Pex come in? For Pex to do its job I'll need a parametrized unit test. This is a unit test that will take arguments. Consider it as a test refactoring thing. Things that are common in the unit test are extracted and put into separate method/function. Pex needs this setup because it will be in charge with generating "traditional" unit tests with test data values that it will determine during its exploration phase. These generated unit test will call the parametrized unit test , passing the necessary arguments.

In our "mock" example we explicitly setup the mock for each unit test. In analogy with the parametrized unit test we need a parametrized Mock object (see Pex Tutorial).


[TestClass]
[PexClass]
public partial class BonusTest
{
[PexMethod]
[PexUseType(typeof(SalesPexMock))]
public void Calculate(ISales aSalesPexMock)
{
PexAssume.IsNotNull(aSalesPexMock);

uint EmployeeID = 9999; //dummy , not important for our test
Bonus target = new Bonus(aSalesPexMock);
double result = target.CalculateForEmployee(EmployeeID);
PexValue.AddForValidation("result", result);
}

}

[PexMock]
public class SalesPexMock : ISales
{
public uint LastYearSalesForEmployee(uint aEmployeeID)
{
var call = PexOracle.Call(this);
uint lys = (uint)call.ChooseFrom("lastYearSales", new uint[] { 29999, 30000, 30001 });
PexValue.Add("lastYearSales", lys);
return lys;
}

public uint CurrentYearSalesForEmployee(uint EmployeeID)
{
var call = PexOracle.Call(this);
uint lys = (uint)call.ChooseFrom("CurrentYearSales", new uint[] {29999, 30000, 30001});
PexValue.Add("CurrentYearSales", lys);
return lys;
}
}


Running the PexExplorartion on the PexMethod will result in three unit test being generated.


[TestMethod]
[PexGeneratedBy(typeof(BonusTest))]
public void Calculate01()
{
PexValue.Generated.Clear();
IPexOracleRecorder oracle = PexOracle.NewTest();
((IPexOracleSessionBuilder)
(oracle.OnCall(0, "SalesPexMock.CurrentYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "CurrentYearSales", (object)30000u);
((IPexOracleSessionBuilder)
(oracle.OnCall(1, "SalesPexMock.LastYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "lastYearSales", (object)30000u);
SalesPexMock salesPexMock = new SalesPexMock();
this.Calculate((ISales)salesPexMock);
global::Microsoft.Pex.Framework.PexValue.Generated.Validate("result", "300");
}

[TestMethod]
[PexGeneratedBy(typeof(BonusTest))]
public void Calculate02()
{
PexValue.Generated.Clear();
IPexOracleRecorder oracle = PexOracle.NewTest();
((IPexOracleSessionBuilder)
(oracle.OnCall(0, "SalesPexMock.CurrentYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "CurrentYearSales", (object)29999u);
((IPexOracleSessionBuilder)
(oracle.OnCall(1, "SalesPexMock.LastYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "lastYearSales", (object)30000u);
SalesPexMock salesPexMock = new SalesPexMock();
this.Calculate((ISales)salesPexMock);
global::Microsoft.Pex.Framework.PexValue.Generated.Validate("result", "299");
}

[TestMethod]
[PexGeneratedBy(typeof(BonusTest))]
public void Calculate03()
{
PexValue.Generated.Clear();
IPexOracleRecorder oracle = PexOracle.NewTest();
((IPexOracleSessionBuilder)
(oracle.OnCall(0, "SalesPexMock.CurrentYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "CurrentYearSales", (object)30001u);
((IPexOracleSessionBuilder)
(oracle.OnCall(1, "SalesPexMock.LastYearSalesForEmployee(UInt32)")))
.ChooseAt(0, "lastYearSales", (object)29999u);
SalesPexMock salesPexMock = new SalesPexMock();
this.Calculate((ISales)salesPexMock);
global::Microsoft.Pex.Framework.PexValue.Generated.Validate("result", "550");
}


Of course meanwhile you verified the test coverage results :-) in the Pex report or via the Visual Studio Test Coverage facility.

So with the PexMock framework expectations for the indivudual unit test are setup , just like we did in our Rhino.Mock example. Of we had to help the Pex mock framework a little bit just like we did with the parametrized unit test.


Thanks for reading. Of course your comments are most welcome.

Best regards,

Alexander


ps: We already had complaints of Salespersons that find this bonus rule not fair. for example sales-persons that are above the minimum sales amount eligable for extra bonus but are not progressing , don't get it. Like wise someone who is progressing doesn't receive extra incentives.... :-)

Monday, 11 August 2008

Pex - test case 6 (revisited...again)

Hello,

I did some further test on test case 6 . I removed all pex assumptions regarding the dates. This time Pex had no problem finding the test cases necessary for a complete block coverage.

So the PUT states no assumptions regarding the dates;


[TestClass]
[PexClass(typeof(EmployeeServices))]
public partial class EmployeeServicesTest
{
[PexMethod()]
public void DeterminePensionContribution([PexAssumeUnderTest]EmployeeServices target, Employee aEmployee)
{
PexAssume.IsNotNull(aEmployee);
PexAssume.IsTrue(aEmployee.Salary > 1400 && aEmployee.Salary < 10000);
PexAssume.IsTrue(aEmployee.TypeContract == Contract.FullTime || aEmployee.TypeContract == Contract.PartTime);


PensionContribution result = target.DeterminePensionContribution(aEmployee);

PexValue.AddForValidation("BaseContribution", result.BaseContribution);
PexValue.AddForValidation("SupplementaryContribution", result.SupplementaryContribution);

if (aEmployee.Salary <= 3000)
Assert.IsTrue(result.BaseContribution == 80);
if (aEmployee.Salary > 3000)
Assert.IsTrue(result.BaseContribution == 135);
if (aEmployee.Salary <= 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aEmployee.Salary <= 3000 && aEmployee.Age < 45 && aEmployee.TypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aEmployee.Salary <= 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears >= 5)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aEmployee.Salary > 3000 && aEmployee.Age < 45 && aEmployee.ServiceYears >= 5 && aEmployee.TypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aEmployee.Salary > 3000 && aEmployee.Age < 45 && aEmployee.ServiceYears >= 5 && aEmployee.TypeContract == Contract.PartTime)
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aEmployee.Salary > 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears >= 5)
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aEmployee.Salary > 3000 && aEmployee.ServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 30);
}

}


When you run Pex this time it generated none less than 11 unit tests. The generated DateTime value are constructed with the Ticks argument, so it no easy to see the actual dateTime. Actually the first time I run this Pex generated a tick value with a value larger than 32 bit. When running the generated unit tests, an exception was thrown specifying thant the tick value should be between the DateTime.Minvalue.Ticks and DateTimeValue.Maxvalue.Ticks. It seemed to me that i generated a 64-bit value.
I haven't been able to reproduce this error though. May be it is one of the remarks in the release of this version 0.6x. I'm running on Windows XP SP2!

Nevertheless here are the generated unit tests.

public partial class EmployeeServicesTest
{
[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution01()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(1L);
employee = EmployeeFactory.Create
(2, "", "", default(DateTime), s0, 8192u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution02()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(1L);
employee = EmployeeFactory.Create
(2, "", "", default(DateTime), s0, 1983u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "25");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution03()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(1L);
employee = EmployeeFactory.Create
(2, "", "", default(DateTime), s0, 8192u, Contract.PartTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution04()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(647341024241516544L);
DateTime s1 = new DateTime(1L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "25");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution05()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(647341024241516544L);
DateTime s1 = new DateTime(1L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "15");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution06()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(647341024241516544L);
DateTime s1 = new DateTime(1L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.PartTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution07()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(1262076461318144L);
DateTime s1 = new DateTime(634715138118123520L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.PartTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "30");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution08()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(56623104001835008L);
DateTime s1 = new DateTime(634715138118123520L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "15");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution09()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(647341024241516544L);
DateTime s1 = new DateTime(634715138118123520L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "30");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution10()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(647341024241516544L);
DateTime s1 = new DateTime(1L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.PartTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "0");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContribution11()
{
PexValue.Generated.Clear();
Employee employee;
DateTime s0 = new DateTime(633532870975488000L);
DateTime s1 = new DateTime(126226978751643648L);
employee = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices employeeServices = new EmployeeServices();
this.DeterminePensionContribution(employeeServices, employee);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "15");
}

}


When you investigate the coverage results for Visual Studio and the output from Pex, you will see that both confirm the 100% coverage result.

Part of the Pex report , coverage section (click on picture for better view):



What about my "business" assumptions? Maybe it would be easier to incorporate them as pre-conditions in the code under test. For example Start date of the contract should be greater than 25/May/1968 in the setter method for Startdatecontract of our Employee class. Non-compliance would than result in an exception being thrown. how will pex cope with them?


Thanks for reading. As always, feel free to comment.

Best regards,

Alexander

Pex - test case 6 (revisited)

Hello,

In the previous case I tried to come up with a scenario where a method takes a "complex" type instead of primitive types like integers of booleans. The exercise didn't go as expected. By the way , the latest downloadable version of Pex is 0.6.30728.0

To see whether pex still works on a method with primitive types, I re-wrote the method as follows.


public PensionContribution DeterminePensionContribution2(
uint aSalary,
uint aAge,
uint aServiceYears,
Contract aTypeContract)
{
//no pre-codition checks ....

PensionContribution contribution = new PensionContribution();

if (aSalary <= 3000)
{
if (aAge < 45)
{
if (aTypeContract == Contract.FullTime)
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 15;
}
else
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 0;
}
}
else
{
if (aServiceYears < 5)
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 15;
}
else
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 25;
}
}
}
else
{
if (aAge < 45)
{
if (aServiceYears < 5)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 30;
}
else
{
if (aTypeContract == Contract.FullTime)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 25;
}
else
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 20;
}
}
}
else
{
if (aServiceYears < 5)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 30;
}
else
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 20;

}
}

}
return contribution;
}



The PUT method looks like this.



[PexMethod]
public void PUTDeterminePensionContribution(
[PexAssumeUnderTest]EmployeeServices target,
uint aSalary,
uint aAge,
uint aServiceYears,
Contract aTypeContract
)
{
PexAssume.IsTrue(aAge > 18 && aAge < 75);
PexAssume.IsTrue(aServiceYears > 0 && aServiceYears < 50);
PexAssume.IsTrue(aSalary > 1400 && aSalary < 10000);
PexAssume.IsTrue(aTypeContract == Contract.FullTime || aTypeContract == Contract.PartTime);

PensionContribution result = target.DeterminePensionContribution
(aSalary, aAge, aServiceYears, aTypeContract);
PexValue.AddForValidation("BaseContribution", result.BaseContribution);
PexValue.AddForValidation("SupplementaryContribution", result.SupplementaryContribution);

if (aSalary <= 3000)
Assert.IsTrue(result.BaseContribution == 80);
if (aSalary > 3000)
Assert.IsTrue(result.BaseContribution == 135);
if (aSalary <= 3000 && aAge >= 45 && aServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aSalary <= 3000 && aAge < 45 && aTypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aSalary <= 3000 && aAge >= 45 && aServiceYears >= 5)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aSalary > 3000 && aAge < 45 && aServiceYears >= 5 && aTypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aSalary > 3000 && aAge < 45 && aServiceYears >= 5 && aTypeContract == Contract.PartTime)
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aSalary > 3000 && aAge >= 45 && aServiceYears >= 5)
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aSalary > 3000 && aServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 30);
}


In no time Pex finds the test cases for full block coverage











targetaSalaryaAgeaServiceYearsaTypeContractBaseContributionSupplementaryContributionSummary/ExceptionError Message
1...8192642FullTime13530
2...81926430FullTime13520
3...8192312FullTime13530
4...81923130FullTime13525
5...1532642FullTime8015
6...15326430FullTime8025
7...1532312FullTime8015
8...8192642PartTime13530
9...1532312PartTime800
10...81923130PartTime13520



So there is nothing "wrong" with the method under test. The problem when you investigate the output window, lies maybe in the calculation of the "age" and "serviceYears" properties.


00:00:00.0> starting execution
00:00:00.0> reflecting tests
00:00:00.3> TestProject1
00:00:00.3> EmployeeTest
00:00:00.3> DeterminePensionContributionIntern(Employee)
maxconstraintsolvertime - 1s (constraint solver time out after 1 seconds)
[execution] 4 runs
[execution] 15 runs
[test] (run 20) DeterminePensionContribution
[test] (run 21) DeterminePensionContribution
[execution] 21 runs, 49/81 blocks covered
[test] (run 25) DeterminePensionContribution
[execution] 29 runs, 49/81 blocks covered
[execution] 41 runs, 49/81 blocks covered
[modelsearch] could not flip 2 branches in a row
[execution] 50 runs, 49/81 blocks covered
[execution] imprecision at testingPEX.Employee.differenceInYears, offset 0x26
[modelsearch] could not flip 4 branches in a row
[execution] 77 runs (97,40% unique paths), 49/81 blocks covered
timeout - 120s
Timeout, 1 times
MaxConstraintSolverTime, 60 times
[coverage] 49/81 block (60,49%)

00:02:01.4> [finished] 3 generated tests (0 failures), 00:02:01.3757768.
-- 0 critical errors, 0 errors, 0 warnings.

[reports] skipping report generation

EXPLORATION SUCCESS


monitored process exited with Success (0)
finished



Pex needs to find dateTimes in order to test the conditions specified in the method. I used a helper method to calculate the years to date for the age and serviceyears:

private uint differenceInYears(System.DateTime aDate1, System.DateTime aDate2)
{
//missing checks ....
//approximative
uint years = (uint)(aDate1.Year - aDate2.Year);
if (aDate1.Month == aDate2.Month)
{
if (aDate2.Day < aDate1.Day) years = years - 1;
}
return years;
}



Could it be the unsigned integer type declarations? Or are the assumptions wrongly formulated? Or both? What does "could not flip 2 branches in a row" mean?

I'll try to do some more test around this. Feel free to comment!

Thanks for reading.

Best regards,

Alexander

Wednesday, 6 August 2008

Pex - Test Case 6

Hello,

In the previous Pex posts I used examples that used primitive types and/or strings as parameters for methods under test. In this post I will "test" Pex how it wil cope with a method that accepts a "complex type". This test scenario is similar to the case study in Pex - Test case 3 except we don't work directly with primitive types a method parameters or return types.


Suppose the HR department asked you to include a new module in their Salary-application that will determine the contribution each employee has to make in a private pension fund. The salary administration uses the following desicion table.



While analysing the table you conclude you can simplify the table to help you code this rule into c#.



You already have some classes in the application :


public class Employee
{
//missing elementary checks in order to keep code concise....
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
private DateTime _BirthDate;
public DateTime BirthDate
{
get { return this._BirthDate; }
set {
this._BirthDate = value;
this.Age = differenceInYears(_BirthDate, DateTime.Now);
}
}
private DateTime _StartDateContract;
public DateTime StartDateContract {
get { return _StartDateContract; }
set {
_StartDateContract=value;
this.ServiceYears = differenceInYears(_StartDateContract, DateTime.Now);
} }
public uint Salary { get; set; }
public Contract TypeContract { get; set; }
public uint Age { get; set; }
public uint ServiceYears { get; set; }
private uint differenceInYears(DateTime aDate1, DateTime aDate2)
{
//missing checks ....
//approximative
uint years = (uint)(aDate1.Year - aDate2.Year);
if (aDate1.Month == aDate2.Month)
{
if (aDate2.Day < aDate1.Day) years = years - 1;
}
return years;
}
}

public enum Contract
{
FullTime=0,
PartTime=1
}

public struct PensionContribution
{
public uint BaseContribution{get;set;}
public uint SupplementaryContribution {get;set;}
}


The rule has also been translated into c#. The method DeterminePensionContribution has a parameter of the type Employee as described in the above code snippet. The return type is the structure PensionContribution.


public class EmployeeServices
{
public PensionContribution DeterminePensionContribution(Employee aEmployee)
{
//no pre-condition checks .....

PensionContribution contribution = new PensionContribution ();

if (aEmployee.Salary <= 3000)
{
if (aEmployee.Age < 45)
{
if (aEmployee.TypeContract == Contract.FullTime)
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 15;
}
else
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 0;
}
}
else
{
if (aEmployee.ServiceYears < 5)
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 15;
}
else
{
contribution.BaseContribution = 80;
contribution.SupplementaryContribution = 25;
}
}
}
else
{
if (aEmployee.Age < 45)
{
if (aEmployee.ServiceYears < 5)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 30;
}
else
{
if (aEmployee.TypeContract == Contract.FullTime)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 25;
}
else
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 20;
}
}
}
else
{
if (aEmployee.ServiceYears < 5)
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 30;
}
else
{
contribution.BaseContribution = 135;
contribution.SupplementaryContribution = 20;

}
}

}
return contribution;
}






}


In a "test-after-you-code" scenario, you could use the desicion table to specify the values for actual for the different properties of a employee object that is being passed to the method under test. So 9 unit tests would be sufficient to touch all corners of the code.

A data-driven in test in Visual Studio is somewhat difficult because we are not dealing with simple datatype parameters. We could serialize the different instances and save it in string form into an external data source.

Can Pex help generate the necessary unit test to fully test our code?

Before heading to the PUT (Parameterized Unit Test) I would like to refer you to the Pex tutorial for more background information on setting up PexMethods.

A new element in this case study is the use of a factory method to create our Employee object that will be used in the generated unit test. Pex will call this method with appropriate values that it has discovered during its exploration of the code in the generated unit tests. The cool part is that Pex will first complain about not being able to create a Employee object but then suggest a soulution for it : the Factoty method and generates it for us.


[PexFactoryClass]
public partial class EmployeeFactory
{
[PexFactoryMethod(typeof(Employee))]
public static Employee Create(
int i0,
string s0,
string s1,
DateTime dt0,
DateTime dt1,
uint ui0,
Contract c0
)
{

Employee e0 = new Employee();
e0.EmployeeID = i0;
e0.FirstName = s0;
e0.LastName = s1;
e0.BirthDate = dt0;
e0.StartDateContract = dt1;
e0.Salary = ui0;
e0.TypeContract = c0;
return e0;
}

}

In the generated unit test , this factory method is used as follows:

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_095656_001()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(626406282140467200L);
DateTime s1 = new DateTime(631149005309067264L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}


Let's return to the PUT. The following PUT was based on a PexMethod that you can let the Visual Studio Pex integration build for you.
During its explorartion Pex will try out different value for the properties of our Employee object. The assumptions should help Pex create reasonable test cases. The code in this example did not enforce them in order to keep the code somewhat limited in size.

Because this method will be used for all generated unit test, the asserts have to be more general than you would normally do for a single unit test.


[TestClass]
[PexClass(typeof(EmployeeServices))]
public partial class EmployeeServicesTest
{
[PexMethod(MaxConstraintSolverTime = int.MaxValue, Timeout = int.MaxValue)]
public void DeterminePensionContribution([PexAssumeUnderTest]EmployeeServices target, Employee aEmployee)
{
PexAssume.IsNotNull(aEmployee);
PexAssume.IsTrue(aEmployee.BirthDate < new DateTime(1992, 1, 1) && aEmployee.BirthDate > new DateTime(1955, 1, 1));
PexAssume.IsTrue(aEmployee.StartDateContract < DateTime.Now && aEmployee.StartDateContract > new DateTime(1968, 1, 1));
PexAssume.IsTrue(aEmployee.BirthDate < aEmployee.StartDateContract);
PexAssume.IsTrue(aEmployee.Age > 18);
PexAssume.IsTrue(aEmployee.Salary > 1400 && aEmployee.Salary < 10000);
PexAssume.IsTrue(aEmployee.TypeContract == Contract.FullTime || aEmployee.TypeContract == Contract.PartTime);


PensionContribution result = target.DeterminePensionContribution(aEmployee);

PexValue.AddForValidation("BaseContribution", result.BaseContribution);
PexValue.AddForValidation("SupplementaryContribution", result.SupplementaryContribution);

if (aEmployee.Salary <= 3000)
Assert.IsTrue(result.BaseContribution == 80);
if (aEmployee.Salary > 3000)
Assert.IsTrue(result.BaseContribution == 135);
if (aEmployee.Salary <= 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aEmployee.Salary <= 3000 && aEmployee.Age < 45 && aEmployee.TypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 15);
if (aEmployee.Salary <= 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears >= 5)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aEmployee.Salary > 3000 && aEmployee.Age < 45 && aEmployee.ServiceYears >= 5 && aEmployee.TypeContract == Contract.FullTime)
Assert.IsTrue(result.SupplementaryContribution == 25);
if (aEmployee.Salary > 3000 && aEmployee.Age < 45 && aEmployee.ServiceYears >= 5 && aEmployee.TypeContract == Contract.PartTime)
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aEmployee.Salary > 3000 && aEmployee.Age >= 45 && aEmployee.ServiceYears >= 5 )
Assert.IsTrue(result.SupplementaryContribution == 20);
if (aEmployee.Salary > 3000 && aEmployee.ServiceYears < 5)
Assert.IsTrue(result.SupplementaryContribution == 30);
}

}


Running the Pex exploration several times (and after quite a while) I got results but no full coverage. Maybe the assumptions and/or asserts are not "pex-friendly"?



The generated unit tests only cover half of the code blocks.

public partial class EmployeeServicesTest
{

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_110101_000()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(619794039740301312L);
DateTime s1 = new DateTime(631148677322244096L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_110101_001()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(619794039740301312L);
DateTime s1 = new DateTime(631148677322244096L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "25");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_110121_002()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(619794039740301312L);
DateTime s1 = new DateTime(631148677322244096L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.PartTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_110200_003()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(626094836655304192L);
DateTime s1 = new DateTime(633479291125776385L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "30");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_110201_004()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(626094836655304192L);
DateTime s1 = new DateTime(633479291125776385L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "15");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_112335_000()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(609681269903603218L);
DateTime s1 = new DateTime(621044308151107616L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_112335_001()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(609681269903603218L);
DateTime s1 = new DateTime(621044308151107616L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "25");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_112422_002()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(609681269903603218L);
DateTime s1 = new DateTime(621044308151107616L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.PartTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "20");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_115647_003()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(627392712227689792L);
DateTime s1 = new DateTime(633479779357556736L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 8192u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "135");
PexValue.Generated.Validate("SupplementaryContribution", "30");
}

[TestMethod]
[PexGeneratedBy(typeof(EmployeeServicesTest))]
public void DeterminePensionContributionEmployeeServicesEmployee_20080806_115718_004()
{
PexValue.Generated.Clear();
Employee e0;
DateTime s0 = new DateTime(627392712227689792L);
DateTime s1 = new DateTime(633479779357556736L);
e0 = EmployeeFactory.Create(2, "", "", s0, s1, 1983u, Contract.FullTime);
EmployeeServices es0 = new EmployeeServices();
this.DeterminePensionContribution(es0, e0);
PexValue.Generated.Validate("BaseContribution", "80");
PexValue.Generated.Validate("SupplementaryContribution", "15");
}

}





If you have any suggestions to improve (re-factor) the test code or the code-under- test (test-friendliness), don't hesitate to drop a line.

Thanks for reading.

Best regards,

Alexander

Thursday, 31 July 2008

Pex - test Case 5

Hello,

This post is a continuation in a series of post about Pex, a test case generation tool from Microsoft research. This test scenario is a variation of test case 4. (sorry in advance for the table formatting , I still can not get it right :-(

In the Pex tutorial on p37 you'll see an example of testing a Regular Expression. I didn't find one in the samples though. Too simple maybe?

I'm no hero in regular Expressions. Thankfully there's a library loaded with regular expressions for every kind of application. The following regular expressing is for ensuring strong passwords.















Expression
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,8}$

Description
Password matching expression. Password must be at least 4 characters, no more than 8 characters, and must include at least one upper case letter, one lower case letter, and one numeric digit.

Matches
asD1 | asDF1234 | ASPgo123

Non-Matches
asdf | 1234 | ASDF12345


The code example where the regular expression is implemented in

public class UserAccount
{

private string mPassword;

public string Password
{
get { return mPassword; }
set {
if (value == null)
throw new System.ArgumentNullException("Please specify a valid password. (non-null)");
else
{
bool valid = RegularExpressions.Regex.IsMatch(value, @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,8}$");
if (valid)
mPassword = value;
else
throw new System.ArgumentException("Please specify a Strong Password. ");
}
}
}
}


The pex method (keeping in mind the remarks in the comments from the previous test case: the invariant remark)

[PexMethod()]
public void PasswordTest( string aPassword)
{
UserAccount target = new UserAccount();
target.Password = aPassword;

//no problem calling target.password several times, stays the same
Assert.IsTrue(target.Password != null);
Assert.IsTrue(target.Password.Length != 0);
Assert.IsTrue(target.Password.Length >= 4);
Assert.IsTrue(target.Password.Length <= 8);
Assert.IsTrue(RegularExpressions.Regex.IsMatch(target.Password, @"[A-Z]"));
Assert.IsTrue(RegularExpressions.Regex.IsMatch(target.Password, @"[a-z]"));
Assert.IsTrue(RegularExpressions.Regex.IsMatch(target.Password, @"[0-9]"));


Assert.AreEqual(aPassword, target.Password);
}


We could do this without Pex of course. We have have to come up with several input values to trigger an exception (at least that is what we intended to program and now are verifying) and some test values for some "positive" tests. The equivalence class technique can surely help us out.

Valid equivalence classes

  • bigger or equal than 4 characters

  • smaller or equal than 8 characters

  • include at least one upper case letter

  • include at least one lower case letter

  • include at least one numeric digit



Invalid equivalence classes

  • not null, non-existing

  • smaller than 4 characters

  • bigger than 8 characters

  • no upper case letter

  • no lower case letter

  • no numeric digit



You normally start with searching a physical test case (password value) that covers all the valid equivalence classes and for each invalid equivalence class you should also make a test case (i.e. unit test as well). Try not to overlap any invalid equivalance classes so you can be sure that the forced invalidity throwed the exception.

  • asDF1234 (covers all valid classes)

  • null (non existing)

  • Az1 (smaller than 4 chars)

  • ASDf12345 (etc)

  • abcd1

  • ABCD1

  • aBcDefGh



So 7 test cases should be enough ?

What would Pex do? It should be as good as this (if not better , at least the tutorial almost implies so).
If you would let the PexMethod work on the password setter method, you would get following results.




















RunaPasswordSummary/ExceptionError Messag
1nullArgumentNullExceptionValue cannot be null.
Parameter name: Please specify a valid password. (non-null)
2""""ArgumentExceptionPlease specify a Strong Password.
3\0ArgumentExceptionPlease specify a Strong Password.
4\u8000ArgumentExceptionPlease specify a Strong Password.
6\0\0ArgumentExceptionPlease specify a Strong Password.
78ArgumentExceptionPlease specify a Strong Password.
8\0\nArgumentExceptionPlease specify a Strong Password.
10\0\0\0ArgumentExceptionPlease specify a Strong Password.
11\nArgumentExceptionPlease specify a Strong Password.
12\n\0ArgumentExceptionPlease specify a Strong Password.
16\0\u8000\u8000ArgumentExceptionPlease specify a Strong Password.
21\n\nArgumentExceptionPlease specify a Strong Password.
25\u00062\u00f1\u0016\n\nArgumentExceptionPlease specify a Strong Password.
31\09\u00ef\u0088f\rArgumentExceptionPlease specify a Strong Password.
75\u8000\0\0\0\n\0ArgumentExceptionPlease specify a Strong Password.
830\u0088o]O<  
112\u00062Oo\n\nArgumentExceptionPlease specify a Strong Password.
113\u00062Oo\n  
114\u00062Oo\n\n\n\n\n\n\n\n\n\nArgumentExceptionPlease specify a Strong Password.


Pex established 19 test cases. A little bit more if you would go for the manual approach. But are the test input values better? In terms of "readability" no. But Pex generated some input values that I wouldn't have thought of in the first place.

The empty string was added in the Pex generated approach. In our manual exercise with didn't explicitly took that test case because this was implied in the "smaller than 4 chars" equivalence class. But again testing a little bit too much is maybe better (or saver) than testing too little.

Also the Pex generated unit tests include non alfanumeric signs like * or /. Did I intended to do that with my expression? If not I have to re-write it. With my manual technique I "forget" them. So Pex went a little deeper than my test analysis.
What about Escape characters such as "\n" (new line). pex included them and some tests passed. Was this the way I intended it? Were these the specs from the customer?

The examples I used to get acquinted with Pex may seem academic but I believ show potential for a tool like Pex. How to use it in your day-to-day applications will take some time and study. And like the Pex FAQ already states : it is not Pex versus unit testing (or other forms of testing or QA assurance measures like code analysis for example). I hope the Pex team will (continue to) find sponsers within Microsoft to integrate it in the next releases of Visual studio. At least the pex team already took a patent!.

Thanks for reading.

Best regards,

Alexander

Monday, 28 July 2008

Pex - test case 4

Hello,

I've got one more test scenario for Pex. From comments on my previous post
Case study 3 I understood that the way I specified the PUT was not done correctly (though Pex generated the unit test methods I expected).

A PUT should specify what the intended behaviour of the code under test should do. It should not do this like in a tradiotional unit test for a particular combination of test input but in a more general way.

Sounds reasonable but I find some difficulity in writing them. I'll try to come with a "better" PUT this time.

Our code under test is as follows (Sorry in advance for some bad formatting HTML tables later in the post.):

private string mCode;

public string Code
{
get { return mCode; }
set {
if (value == null )
{
throw new System.ArgumentNullException("Please specify a valid code. (non-null)");
}
else if (value.Length < 4)
{
throw new System.ArgumentException("Please specify a valid code. (bigger than 3 characters.");
}
else if (value.Length > 10)
{
throw new System.ArgumentException("Please specify a valid code. (max 10 characters. including XYZ prefix) ");
}
else if (value.StartsWith("XYZ") == false)
{
throw new System.ArgumentException("Please specify a valid code (starting with XYZ - uppercase !) ");
}
else
{
this.mCode = value;
}

}


With the comments for my PUT in mind , I created the following PUT:

[PexMethod()]
public void CodeTest(string aCode)
{
measurement target = new measurement();
string actual;
target.Code = aCode;
actual = target.Code;
PexValue.AddForValidation("actual", actual);
if (( aCode != null) &&
(aCode.Length > 3) &&
(aCode.Length <= 10) &&
(aCode.StartsWith("XYZ")) )
PexAssert.IsTrue(actual == aCode);

}



Running Pex on this PUT will result in several generated test cases.
(Pex reporting option : Enable via Options/Pex/Reports)






















Parameter Values
RunaCodeactualSummary/Exception
1null  ArgumentNullException:
Value cannot be null.
Parameter name: Please specify a valid code. (non-null)
(testingPEX)
2""  ArgumentException: Please specify a valid code. (bigger than 3 characters. (testingPEX)
3\0\0\0\0\0\0  ArgumentException: Please specify a valid code (starting with XYZ - uppercase !) (testingPEX)
4new string('\0', 14)  ArgumentException: Please specify a valid code. (max 10 characters. including XYZ prefix) (testingPEX)
5XYZZZZZ XYZZZZZ  


Of course we first need to specify the expected exceptions (Pex will suggest these for you !) :

  • Add attribute [PexAllowedExceptionFromAssembly(typeof(ArgumentNullException), "testingPEX")]

  • Add attribute [PexAllowedExceptionFromAssembly(typeof(ArgumentException), "testingPEX")]



The generated unit tests look like this :

public partial class measurementTest
{

[TestMethod]
[PexGeneratedBy(typeof(measurementTest))]
public void CodeTestString_20080728_141020_005()
{
PexValue.Generated.Clear();
this.CodeTest("XYZZZZZ");
PexValue.Generated.Validate("actual", "XYZZZZZ");
}

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[PexGeneratedBy(typeof(measurementTest))]
public void CodeTestString_20080728_143541_000()
{
this.CodeTest((string)null);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
[PexGeneratedBy(typeof(measurementTest))]
public void CodeTestString_20080728_143620_001()
{
this.CodeTest("");
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
[PexGeneratedBy(typeof(measurementTest))]
public void CodeTestString_20080728_143620_002()
{
this.CodeTest("\0\0\0\0\0\0");
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
[PexGeneratedBy(typeof(measurementTest))]
public void CodeTestString_20080728_143620_003()
{
this.CodeTest(new string('\0', 14));
}

}


If you would verify the code coverage results in Visual Studio or via the Pex reporting option, you would see 100% Block coverage.

Pex generated for us the strings to serve as input values. I don't know about you but I think this is pretty clever.

Still I'm a little confused about the PUT I created. If I would delete all the pexAssert statements the exercise would still be the same for the generated unit tests.


[PexMethod()]
public void CodeTest(string aCode)
{
measurement target = new measurement();
string actual;
target.Code = aCode;
actual = target.Code;
PexValue.AddForValidation("actual", actual);
}



Thanks for reading.

Best regards,


Alexander

Friday, 25 July 2008

Pex - Test case 3

Hello,

This post is a continuation of previous posts. Feel free to check them out.


In this example I base my example on the following rule:
In our Order system we need to implement a validation rule to see whether we can accept an order or not. The criteria for acceptance where given to us (luckily) in the form of a decision table. There are three criteria ; an internal customer qualification scheme, the fact if the previous payments of the customer were usually on time and correct and the fact if the customer has sufficient credit possibilities.





Cust-codeaaaabbbbccccuuuu
paymentsOKyynnyynnyynnyynn
CredietLimitOKynynynynynynynyn
                  
AcceptOrderyynnyyynyyyynnnn



public enum CustomerType
{
U_Unknown = 0,
A_Status = 1,
B_Status = 2,
C_Status = 3
}

public class OrderManager
{
public bool IsOrderToBeAccepted(CustomerType aClientType, bool aGoodpaymentHistory, bool aCreditSufficient)
{
bool orderAccept = false;
switch (aClientType) {
case CustomerType.A_Status:
if (aGoodpaymentHistory == true) {
orderAccept = true;
}
else
{
orderAccept = false;
}
break;
case CustomerType.B_Status:
if (aGoodpaymentHistory || aCreditSufficient) {
orderAccept = true;
}
else {
orderAccept = false;
}
break;
case CustomerType.C_Status:
orderAccept = true;
break;
default:
orderAccept = false;
break;
}
return orderAccept;
}
}


With the help to the decsion table (thanks to the analyst :-), we can easily determine the test cases.
There 2 test sitaution (different behaviours) we must account for : the order is accepted or is not accepted. So we need test cases that will result in a non-accepted order and we will need a test case that will result in an accepted order. Those this mean two test cases (i.e. unit tests) are enough. There is always this "test completeness criterium". If this were a black-box test (we don't "see" the code) we could use a tool like Pict33 to propsose the combination of input values.

In this "lab" test we both have design information (desicion table) and our code. The table in this case can help us enough. Each column will represent the values we will supply to the method under test. This means we’ll end up with 16 unit tests to achieve full coverage of our table (and hopefully also of our code). The unit test are there to verify if our “intentions” (i.e. implementing this method with the above rules) were correctly transformed into code. Both test situations (order accepted / order not accepted) should normally be taken into account with these 16 test cases.

We can write out our units tests :


[TestMethod()]
public void isOrderToBeAcceptedCase1()
{
OrderManager target = new OrderManager();
CustomerType ClientType = CustomerType.A_Status;
bool GoodpaymentHistory = false;
bool CreditSufficient = false;
bool expected = false;
bool actual;
actual = target.isOrderToBeAccepted(ClientType, GoodpaymentHistory, CreditSufficient);
Assert.AreEqual(expected, actual);

}

[TestMethod()]
public void isOrderToBeAcceptedCase2()
{
OrderManager target = new OrderManager();
CustomerType ClientType = CustomerType.A_Status;
bool GoodpaymentHistory = true;
bool CreditSufficient = false;
bool expected = false;
bool actual;
actual = target.isOrderToBeAccepted(ClientType, GoodpaymentHistory, CreditSufficient);
Assert.AreEqual(expected, actual);

}

//and so one


Or we make a data-driven unit test.



[DeploymentItem("TestProject1\\OrderAcceptanceCombinations.csv")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV",
"|DataDirectory|\\OrderAcceptanceCombinations.csv",
"OrderAcceptanceCombinations#csv", DataAccessMethod.Sequential)]
[TestMethod]
public void DataDrivenIsOrderAccepted()
{
OrderManager target = new OrderManager();
CustomerType ClientType = (CustomerType)(System.Convert.ToInt32(this.TestContext.DataRow["CustomerType"])) ;
bool GoodpaymentHistory = System.Convert.ToBoolean(this.TestContext.DataRow["PaymentsOK"]);
bool CreditSufficient= System.Convert.ToBoolean(this.TestContext.DataRow["CreditOK"]) ;
bool actual;
bool expected = System.Convert.ToBoolean(this.TestContext.DataRow["AcceptOrder"]) ;
actual = target.isOrderToBeAccepted(ClientType, GoodpaymentHistory, CreditSufficient);
Assert.IsTrue(actual == expected);
}


Bottom line is we achieve acceptable code coverage. We can verify this with the Visual Studio Code Coverage facility of the test framework.

In that respect we can actually reduce the number of test cases and still achieve full coverage. A technique we can use, is "collapsing" the desicion table. If we closely look at our rules, we can detect that several combinations should result in the same result. This means that some conditions are not taken into account (or don’t make sense) in some test cases. For example whether or not a customer with customer-type UnKnown has good creditRecords or even goodpayments records, it doesn’t matter. We don’t trust “Unknown” customers. We don’t allow an order for them! In order words we can reduce the number of test cases for 4 to 1

If we do this exercise for the entire table , we should come up with the following decision table.
Cust-codeaabbbcu
paymentsOKynynn--
CredietLimitOK---yn--
AcceptOrderynyynyn

This would mean we only need to 7 test cases to fully test our rule. Collapsing tables is powerful in terms of test case reduction but can also be “dangerous” while making an error in our reasoning. We could miss out on something. Better some more than too little, I think.

How does Pex help me with testing this class method?

Our pex trigger could look like this :

[PexMethod()]
public void isOrderToBeAcceptedTest( CustomerType ClientType,
bool GoodpaymentHistory,
bool CreditSufficient)
{
OrderManager target = new OrderManager();
bool actual;
actual = target.isOrderToBeAccepted(ClientType,
GoodpaymentHistory,
CreditSufficient);
PexValue.AddForValidation("actual",actual);
}



This parameterized test should reach all our code if certain combination of input values is used. So Pex should be able to work with this test method.

You can launch Pex from within Visual Studio 2008 C# by rightclicking the mouse (or pressing CTRL+F8). You will see a menu option ” Run Pex Exploration”. This will launch Pex in doing it’s job, running your code while generating the right combination of input values for each possible code path and then generating the code for all the test cases.



It is the same number of test cases as with our "manual" approach. So this looks pretty good.



public partial class OrderManagerTest
{
[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143741_000()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.U_Unknown, false, true);
PexValue.Generated.Validate("actual", "False");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143742_001()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.A_Status, false, true);
PexValue.Generated.Validate("actual", "False");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143742_002()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.A_Status, true, true);
PexValue.Generated.Validate("actual", "True");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143742_003()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.B_Status, false, true);
PexValue.Generated.Validate("actual", "True");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143742_004()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.B_Status, true, true);
PexValue.Generated.Validate("actual", "True");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143743_005()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.B_Status, false, false);
PexValue.Generated.Validate("actual", "False");
}

[TestMethod]
[PexGeneratedBy(typeof(OrderManagerTest))]
public void isOrderToBeAcceptedTestCustomerTypeBooleanBoolean_20080725_143743_006()
{
PexValue.Generated.Clear();
this.isOrderToBeAcceptedTest(CustomerType.C_Status, false, true);
PexValue.Generated.Validate("actual", "True");
}

}



We can check the coverage criterium within Visual Studio.



So Pex did an excellent work here. Of course the examples are somewhat academic and only use simple (primitive) types for arguments and/or return values. What about strings or complex types?

My next case study will check out how Pex works on a method accepting a string type. Complex types ....?

Thanks for reading.

Best regards,

Alexander

Thursday, 24 July 2008

Pex - test case 2

Hello,

I'm currently trying out Pex , an automated unit test generator. I'm trying out several scenarios. If you like you can read about a previous test case I tried out. In the same spirit , I'll start of with what you could do without Pex.

The code snippet is as follows. There is a "measurement class" that implements the following method (property).



private int mTemperature;

public int Temperature
{
get { return mTemperature; }
set {
if (value > -274 & value < 1000)
{
mTemperature = value;
}
else
{
throw new System.ArgumentOutOfRangeException("Please .....");
}

}
}

There are two test situations (different behaviours) in this unit to be tested.

  • an integer value is assigned to a field

  • an exception is thrown



If you would use the Equivalence class technique to establish a good test suite, you could come up with following findings.
Valid classes

  • integer value

  • integer value between -274 and 1000



Invalid classes

  • NOT an integer value

  • integer value smaller or equal than -274

  • integer value bigger or equal than 1000



Because the .NET type system gives us a hand (int) , our method only can accept integers. So we only have 1 valid class and two invalid classes or one if you would combine then in one sentence (Not between -274 and 1000). Both approaches should cover our two test situations (assignation and exception).

Now we have to come up with actual values for these classes which shouldn't be too difficult. This would result in two physical test cases (i.e. unit tests). We could pick -30 and 1500 . Or -650 and 0. And so one... All these values should give equal test results while assuring complete coverage.

In case of two invalid classes you will have three test cases :

  • valid : 25

  • invalid 1 : - 300

  • invalid 2 : 1250



A common place for errors are the comparison signs. The Border Value Analysis "technique" stipulates you should pick values around the "borders". In our example these are -274 and 1000. In addition you could also pick values aboove (and/or under) these border values. Any mistake (current or future) should be detected by our set of test cases (i.e. unit tests).

  • valid 1: 273

  • valid 2: 999

  • invalid 1 : - 274

  • invalid 2 : 1000



So which unit tests will Pex generate for us?



[PexMethod()]
public void TemperatureTest(int aTemperature)
{
measurement target = new measurement();
int actual;
target.Temperature = aTemperature;
actual = target.Temperature;
PexValue.AddForValidation("actual",actual);
}








Pex, using the specified PexMethod, explored our code. It took him two runs to come up with the necessary values for temperature. On the last value (int.MinValue : the smalles possible 32-bit integer value in .NET ) it discovered an exception. Just like in "traditional" unit testing you can specify an "expected exception" . meaning when this exception is thrown, the test is succesfull because that is what you ....expected.



If you re-run the PexMethod again you will see two classic unit test.



public partial class measurementTest
{
[TestMethod]
[PexGeneratedBy(typeof(measurementTest))]
public void TemperatureTestInt32_20080725_080720_000()
{
PexValue.Generated.Clear();
this.TemperatureTest(0);
PexValue.Generated.Validate("actual", "0");
}

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[PexGeneratedBy(typeof(measurementTest))]
public void TemperatureTestInt32_20080725_082344_001()
{
this.TemperatureTest(int.MinValue);
}

}


If you run these two unit tests and enable Code Coverage for the target assembly in VS Test framework, you will notice full coverage as understood by VS test framework.

If you compare the Pex analysis with our "manual" analysis you can see that Pex took the minimal set of test cases to achieve full coverage (as understood by Visual Studio). For example someone would change the comparision operator in the method, the unit tests generated by Pex will not detect it in this particular example.


But if you would slightly change the pexMethod (tip from Peli de Halleux) you would see one extra unit test. You need to re-write the assert to verify the border values.

[PexMethod()]
public void TemperatureTest(int aTemperature)
{
measurement target = new measurement();
int actual;
target.Temperature = aTemperature;
actual = target.Temperature;
PexAssert.IsTrue((actual > -274 & actual < 1000));
}




The unit tests generated by Pex would now include the 1000 border value. I don't understand yet why -274 wasn't picked up. I guess it has to do with the fact 0 was the value chosen in the first run. Maybe there are better ways to write this PexMethod? I'll will need to look into the "assumptions" part of Pex (PexAssume class).


public partial class measurementTest
{
[TestMethod]
[PexGeneratedBy(typeof(measurementTest))]
public void TemperatureTestInt32_20080725_111609_000()
{
this.TemperatureTest(0);
}

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[PexGeneratedBy(typeof(measurementTest))]
public void TemperatureTestInt32_20080725_111609_001()
{
this.TemperatureTest(int.MinValue);
}

[TestMethod]
[ExpectedException(typeof(PexAssertionViolationException))]
[PexGeneratedBy(typeof(measurementTest))]
public void TemperatureTestInt32_20080725_111953_002()
{
this.TemperatureTest(1000);
}

}


Is it worthwile to look for test cases but beyond absolute minimal? Well, it dependends. One of the advantages of of having (automated) unit tests is the ability to use them over and over again. In the context of regression testing these test suitses can be of great help to assure that all code that is using your unit (i.e. method) will still function after you made modifications. or least you will know in which situation something will break so you can take appropriate measures (defaults, overloads, release notes ,...).



private int mTemperature;

public int Temperature
{
get { return mTemperature; }
set {
if (value > -274 & value <= 1000)
{
mTemperature = value;
}
else
{
throw new System.ArgumentOutOfRangeException("Please ...");
}

}
}




So if regression testing is of high priority , it think some methods need those extra test cases. They can happily live beside the ones generated by Pex. It is sometimes (value of software; money, safety) better to have "too much" than "too little" test cases I think (even if it means extra (test) code to maintain.

As shown in the example, the way you setup your Pexmethod will influence how Pex "explores" your code. Although the the call to the (setter) method was the same. The Assertion part of the Pexmethod made Pex do an extra effort.

If you have other thoughts or remarks about pex or devloper testing in general, don't hesitate to drop a comment.

Thanks for reading.


Best regards,


Alexander