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.... :-)

7 comments:

Unknown said...

I've just come across your blog posts on Pex.

I've been writing a few posts on writing theories, and have a post on using Pex as a theory explorer you might find interesting.

http://taumuon-jabuka.blogspot.com/2009/01/theory-driven-development-using_11.html

Thanks!
Gary

Richard Majece said...

I think that information from https://writemyessay4me.org/blog/how-to-make-a-class-entertaining will be useful for teachers. Here you can find info on how to make a class entertaining.

Megan Ryan said...

Bonuses are a great motivation for employees. In our marvelous resume company, we also have a very flexible schedule and good bonuses and that's we have good results in our work.

Anonymous said...

Hello! Thank you for sharing useful information. I think it can help many people to solve their problems. Also, if there are people who need help with writing papers I recommend applying to the trustworthy writing service. read more

Jim Rhodes said...

I will read more about this case. I am really interested right now.

Jatin Sethi said...

The comfort and luxury that the Malibu rehab provides for the inmates are highly soothing for the mind and body of the patient, which helps them to overcome the pain during the prolonged treatment period. These facilities are also provided for the close relatives of the patient also. The close relatives are allowed to stay so that the patients do not feel away from home.
inspirational quotes for addicts
overcoming addiction quotes

Jatin Sethi said...

Try not to get it curved, Donald Trump best screwed a porn megastar on the grounds that he had adequate cash to, not, at this point because of the reality Stormy Daniels unquestionably had any diversion in letting him slosh his smaller scale penis around inside of her for a few minutes. What's more, it appears every other person this going for strolls haze of Cheetos soil come to ways of life has ever screwed he both arranged from Russia (his better half) or snatched by methods for the pussy (i.E. Assaulted). All things considered, perhaps that isn't totally valid. I'm sure he's screwed Ivanka as well – have you ever noticeable the manner in which he slobbers over her and creepily talks roughly how provocative she is? It's screwing nauseating – anyway presumably when she transformed into excessively youthful to unquestionably comprehend what changed into happening. Goodness, yet pause, that is still assault, and I previously recorded that. Poo. Alright, in this way, I bet the posting isn't so long. Donald Trump has easiest gotten his dick sodden gratitude to cash and assault. Despite the fact that, while you consider onconsideration it, the money factor is type of a way of pressuring a female into drowsing with you (specifically in case you're her chief), so isn't that still only a state of mellow assault? Well … that is correct guardians, you heard it directly here first: Donald Trump is an attacker. If not for the money that his daddy provided for him (you consider, that "unobtrusive" advance of one million dollars), he would one be able to hundred% unquestionably by the by be a virgin.
It couldn't be any more obvious, speakme about sex can be a giggle without being only fapping to flicks of screwing throughout the day extensive. Possibly it's about time you gave your dick a pleasantly merited ruin and roused your musings for an exchange – you comprehend, that other area that your blood should be streaming while it's not, at this point continually being rerouted on your dick because of the reality you are endeavoring to fill a void in your self with customary surges of porn. In the event that you hold making your self experience great constantly, in what capacity should you ever detect troubled! That is the soul, ol' entertainment …
yespornplease
yespornplease.com