I am trying to re-write a method using TDD.
Here is the original method without the database logic and below it is my attempt to re-write it. Its not complete as I just hit a wall and do not understand how to finish it.
I’m trying to learn and can’t work it out so need some help. Thanks for reading.
//Original Method
public class StringCalculator()
{
public List<ShipmentInformation> GetBoxRange(int BoxSize, int Quantity, ref string DVDType, ref string ErrorMessage)
{
//Get Connection String
//Do Database Queries and return shipmentInfo variable as List<ShipmentInformation>
var DVDTotals = shipmentInfo.GroupBy(x => x.DVDType).Select(x => new { DVDType = x.Key, Total = x.Sum(y => (y.DVDEndID - y.DVDStartID) + 1) });
if (DVDTotals.Count() == 0 || DVDTotals.All(x => x.Total < Quantity))
{
ErrorMessage = "There is not enough data to create a shipment based on the quantity";
return new List<ShipmentInformation>();
}
//Select the one with the largest amount of stock
var LargestDVDType = DVDTotals.Aggregate((l, r) => l.Total > r.Total ? l : r).DVDType;
var LargestDVDTypeSelection = shipmentInfo.Where(x => x.DVDType == LargestDVDType);
long previousDVDStartID = 0;
//Make sure ranges are sequential
List<ShipmentInformation> SequentialBoxResults = new List<ShipmentInformation>();
foreach (var item in LargestDVDTypeSelection)
{
if (item.DVDStartID - previousDVDStartID == BoxSize || previousDVDStartID == 0)
{
SequentialBoxResults.Add(item);
}
else
{
SequentialBoxResults.Clear();
SequentialBoxResults.Add(item);
}
previousDVDStartID = item.DVDStartID;
if (BoxSize * SequentialBoxResults.Count == Quantity)
{
break;
}
}
if (SequentialBoxResults.Count == 0 || BoxSize * SequentialBoxResults.Count != Quantity)
{
ErrorMessage = "There are no available ranges to create a shipment based on the quantity";
return new List<ShipmentInformation>(); ;
}
List<ShipmentInformation> Results = new List<ShipmentInformation>();
var Genres = SequentialBoxResults.GroupBy(x => x.Genre);
foreach (var Genre in Genres)
{
var orderedGenres = Genre.OrderBy(x => x.DVDStartID);
ShipmentInformation shipment = new ShipmentInformation();
shipment.Genre = Genre.Key;
shipment.DVDStartID = orderedGenres.First().DVDStartID;
var lastItem = orderedGenres.Last();
shipment.DVDEndID = lastItem.DVDEndID;
shipment.DVDType = lastItem.DVDType;
Results.Add(shipment);
}
//We have our shipment recordsnow split them up if any are over 75000
for (int i = 0; i < Results.Count; i++)
{
long rangeSize = Results[i].DVDEndID - Results[i].DVDStartID + 1;
double noOfLoops = Math.Ceiling(rangeSize / 75000D);
long remainder = rangeSize % 75000;
if (noOfLoops > 1)
{
bool AddedRecord = false;
for (int j = 0; j < noOfLoops; j++)
{
long shipmentSize = 0;
if (j == (noOfLoops - 1))
{
shipmentSize = remainder;
if (AddedRecord)
Results.Add(new ShipmentInformation() { DVDStartID = Results.Last().DVDEndID + 1, DVDEndID = Results.Last().DVDEndID + 1 + (shipmentSize - 1), Genre = Results.Last().Genre });
else
Results.Add(new ShipmentInformation() { DVDStartID = Results[i].DVDEndID + 1, DVDEndID = Results[i].DVDEndID + 1 + (shipmentSize - 1), Genre = Results[i].Genre });
}
else
{
shipmentSize = 75000;
if (j == 0)
{
Results[i].DVDEndID = Results[i].DVDStartID + (shipmentSize - 1);
}
else if (j == 1)
{
Results.Add(new ShipmentInformation() { DVDStartID = Results[i].DVDEndID + 1, DVDEndID = Results[i].DVDEndID + 1 + (shipmentSize - 1), Genre = Results[i].Genre });
AddedRecord = true;
}
else
{
Results.Add(new ShipmentInformation() { DVDStartID = Results.Last().DVDEndID + 1, DVDEndID = Results.Last().DVDEndID + 1 + (shipmentSize - 1), Genre = Results.Last().Genre });
AddedRecord = true;
}
}
}
}
}
return Results;
}
}
//New Method with Tests
public List<ShipmentInformation> GetBoxRange(int BoxSize, int Quantity, DateTime CutOffDate, IDataProvider Provider, ref string DVDType, ref string ErrorMessage)
{
if (BoxSize == 0)
{
ErrorMessage = "Please enter a BoxSize";
}
if (Quantity == 0)
{
ErrorMessage = "Please enter a Quantity";
}
if (!String.IsNullOrWhiteSpace(ErrorMessage))
{
return new List<ShipmentInformation>();
}
List<ShipmentInformation> Data = Provider.GetData();
if (Data.Count == 0)
{
ErrorMessage = "Database failed to return data";
return new List<ShipmentInformation>();
}
List<ShipmentInformation> OrderedData = GetSequentialBoxes(Data, BoxSize, Quantity);
if (OrderedData.Count == 0)
{
ErrorMessage = "No sequential data found";
return new List<ShipmentInformation>();
}
//I'm not sure how to continue from here - I started writing GetRecordsPerGenre but got lost in what I'm trying to do. I still need to check if size is over 75000
return OrderedData;
}
public virtual List<ShipmentInformation> GetSequentialBoxes(List<ShipmentInformation> Data, int BoxSize, int Quantity)
{
if (Data.Count == 0)
return new List<ShipmentInformation>();
var orderedData = Data.OrderBy(x => x.DVDStartID);
if (!orderedData.SequenceEqual(Data))
return new List<ShipmentInformation>();
var DVDTotals = Data.GroupBy(x => x.DVDType).Select(x => new { DVDType = x.Key, Total = x.Sum(y => (y.DVDEndID - y.DVDStartID) + 1) });
if (DVDTotals.Count() == 0 || DVDTotals.All(x => x.Total < Quantity))
{
return new List<ShipmentInformation>();
}
var LargestDVDType = DVDTotals.Aggregate((l, r) => l.Total > r.Total ? l : r).DVDType;
Data = Data.Where(x => x.DVDType == LargestDVDType).ToList();
List<ShipmentInformation> returnData = new List<ShipmentInformation>();
long previousDVDStartID = 0;
foreach (var item in Data)
{
if (previousDVDStartID == 0 || item.DVDStartID - previousDVDStartID == BoxSize)
{
returnData.Add(item);
}
else
{
returnData.Clear();
returnData.Add(item);
}
previousDVDStartID = item.DVDStartID;
if (returnData.Count * BoxSize == Quantity)
break;
}
return returnData.OrderBy(x => x.DVDStartID).ToList();
}
public List<ShipmentInformation> GetRecordsPerGenre(List<ShipmentInformation> Data)
{
List<ShipmentInformation> Results = new List<ShipmentInformation>();
var Genres = Data.GroupBy(x => x.Genre);
foreach (var Genre in Genres)
{
var orderedGenres = Genre.OrderBy(x => x.DVDStartID);
ShipmentInformation shipment = new ShipmentInformation();
shipment.Genre = Genre.Key;
shipment.DVDStartID = orderedGenres.First().DVDStartID;
var lastItem = orderedGenres.Last();
shipment.DVDEndID = lastItem.DVDEndID;
shipment.DVDType = lastItem.DVDType;
Results.Add(shipment);
}
return Results;
}
//Tests
[TestFixture]
public class GetBoxRangeMethod
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[TestCase(0, 1)]
[TestCase(1, 0)]
public void ZeroValuesForBoxSizeOrQuantity_ReturnsErrorAndEmptyList(int BoxSize, int Quantity)
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(BoxSize, Quantity, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyBookTypeString_ReturnsErrorAndEmptyList()
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyDBData_ReturnsErrorAndEmptyList()
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyOrderedData_ReturnsErrorAndEmptyList()
{
FakeShipmentCalculator sc = new FakeShipmentCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>() { new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" } });
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
}
//Integration test
[TestFixture]
public class GetDataMethod
{
}
[TestFixture]
public class GetSequentialBoxes
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[Test]
public void MethodCalled_DoesntReturnNull()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetSequentialBoxes(new List<ShipmentInformation>(), 100, 1);
Assert.IsNotNull(result);
}
[Test]
public void EmptyDataPassedIn_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetSequentialBoxes(new List<ShipmentInformation>(), 100, 1);
Assert.AreEqual(0, result.Count);
}
[Test]
public void OrderedData_ReturnsOrderedData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, 2, 1);
ShipmentInformation firstItem = result.FirstOrDefault();
ShipmentInformation secondItem = result.LastOrDefault();
Assert.IsTrue(firstItem.StartPP == 1 && secondItem.StartPP == 3);
}
[Test]
public void UnOrderedData_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int NUMBER_GREATER_THAN_ONE = 2;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, NUMBER_GREATER_THAN_ONE, 1);
Assert.AreEqual(0, result.Count);
}
[Test]
public void SequenceJumps_ClearsListAndStartsAgain()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, 2, 1);
Assert.IsTrue(result.First().StartPP == 5);
}
[Test]
public void LargestNumberOfItemsWithSameChipType_ReturnsDataWithOnlyThatChipType()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "B", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, 1);
Assert.IsTrue(result.All(x => x.ChipType == "A"));
}
[Test]
public void TotalNumberOfRecordsPerChipTypeLessThanQuantity_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "B", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
int Quantity = 5;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, Quantity);
Assert.AreEqual(0, result.Count);
}
[Test]
public void DataReturned_WhenQuantityReached()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
int Quantity = 4;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, Quantity);
Assert.AreEqual(2, result.Count);
}
}
[TestFixture]
public class GetRecordsPerGenre
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[Test]
public void MethodCalled_DoesntReturnNull()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(new List<ShipmentInformation>());
Assert.IsNotNull(result);
}
[Test]
public void EmptyDataPassedIn_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(new List<ShipmentInformation>());
Assert.AreEqual(0, result.Count);
}
[Test]
public void Data_ReturnsGroupedByData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation();
data.ChipType = "A";
data.ReqNo = "B";
data.JacketNo="C";
data.StartPP = 1;
data.EndPP = 2;
inputData.Add(data);
data = new ShipmentInformation();
data.ChipType = "A";
data.ReqNo = "B";
data.JacketNo = "C";
data.StartPP = 1;
data.EndPP = 2;
inputData.Add(data);
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(inputData);
Assert.AreEqual(1, result.Count);
}
}
At Jon, you may be biting off too much for a noob. implement TDD with a new feature, not an existing one. With this scenario, not only are you introducing testing, TDD, mocking. but now you have also introduced refactoring (yet another new concept).
once you have the the basics of TDD on greenfield code, you can then apply it brownfield code along with refactoring.
one last note: TDD doesn’t require mocking, mocking is a separate concept that plays well with TDD, but it’s not required. TDD, simply put: is driving out design through tests.