Personally I think that there are a few key principles.
Tests should, where possible, assume that they have run previously and failed.
Some tests will want to validate a feature, whilst others will need to just "drive through" a feature on the way to test something else.
I can scale out and use many machines, so that I can run a lot of tests in parallel, and I am not time limited on a run.
With those in mind, I would, where possible:
- Have each test generate unique data for itself that does not rely on a previous test passing
- Have the logic for execution separate from validation code
so for your example I would create and execute six separate "objects" for each of your actions:Registering a support ticket, adding a comment, replying to a request for more information, uploading additional information and close a ticket.
Then I would string them together in six test cases as follows:
- Register support ticket and validate
- Register a support ticket, then add a comment and validate
- Register a support ticket, then add a comment, reply with request for more information and validate
- Register a support ticket, then add a comment, reply with request for more information, upload more information and validate
- Register a support ticket, then add a comment, reply with request for more information, upload more information comment that issue is fixed validate
- Register a support ticket, then add a comment, reply with request for more information, upload more information comment that issue is fixed, close and validate
As I test registration 6 times, I would build up a test data object, and use different test data each time so I can test a different scenario each time the registration occurs as well.
So the main "cost" is execution time as I execute 6 long tests as opposed to one (but using open source tools means I can scale out easily) The use of objects means that I don't have lots of duplicated code everywhere. This also means that if the register a ticket part of the application is changed, I only change one object in one place and all the tests keep running.
Here is this approach in code for a single "object" using "Parkcalc".
[TestMethod]
public void EconomyLessThanOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:59", "AM", "today");
}
[TestMethod]
public void EconomyExactlyOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "11:00", "AM", "today");
}
[TestMethod]
public void EconomyMoreThanOneHour()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "11:01", "AM", "today");
}
[TestMethod]
public void EconomyJustLessThanOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "9:59", "AM", "today+1");
}
public void EconomyExactlyOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:00", "AM", "today+1");
}
[TestMethod]
public void EconomyJustMoreThanOneDay()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "10:01", "AM", "today+1");
}
[TestMethod]
public void EconomyJustLessThanOneWeek()
{
Parking.CalculateAndVerify(ParkingType.EconomyParking, "10:00", "AM", "today", "9:59", "AM", "today+7");
}
And the implementation of CalculateAndVerify ...
namespace Parkcalc.Logical
{
public class Parking
{
public static void OpenHome()
{
Physical.NavigateTo.Homepage();
}
public static void Calculate(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
inDate = Utility.CalculateDate(inDate);
outDate = Utility.CalculateDate(outDate);
Physical.Parking.Calculate(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
}
public static void CalculateAndVerify(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
Calculate(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
Verification.Verify.VerifyResult(parkingType, inTime, inAMPM, inDate, outTime, outAMPM, outDate);
}
}
}
and the next layer down
namespace Parkcalc.Physical
{
public static class Parking
{
public static void Calculate(ParkingType parkingType, string inTime, string inAMPM, string inDate, string outTime, string outAMPM, string outDate)
{
NavigateTo.Homepage();
Browser.SetValue(Controls.ParkingCalculator.ddlLot,EnumValues.GetParkingType(parkingType));
if (!string.IsNullOrEmpty(inTime))
{
Browser.SetValue(Controls.ParkingCalculator.txtEntryTime, inTime);
}
if (!string.IsNullOrEmpty(inAMPM))
{
Browser.SetValue(Controls.ParkingCalculator.rdoEntryTimeAMPM, inAMPM);
}
if (!string.IsNullOrEmpty(inDate))
{
Browser.SetValue(Controls.ParkingCalculator.txtEntryDate, inDate);
}
if (!string.IsNullOrEmpty(outTime))
{
Browser.SetValue(Controls.ParkingCalculator.txtExitTime, outTime);
}
if (!string.IsNullOrEmpty(outAMPM))
{
Browser.SetValue(Controls.ParkingCalculator.rdoExitTimeAMPM, outAMPM);
}
if (!string.IsNullOrEmpty(outDate))
{
Browser.SetValue(Controls.ParkingCalculator.txtExitDate, outDate);
}
Browser.Invoke(Controls.ParkingCalculator.btnCalculate);
}
public static string GetResult()
{
return Browser.GetValue(Controls.ParkingCalculator.txtResult);
}
}
}
I have a full working C# example using WatiN here ... http://testingstax.codeplex.com/SourceControl/changeset/view/7938#
For test data I generally build a data generator and add unique random strings so that there is no data collisions.