Samples posted to github here.
DAY 1: TAXES ARE VALIDATED AS THEY ARE ADDED TO CITY
- Tax must be created with its 3 properties: TaxType, StartDate, and EndDate, none of which can be null.
- StartDate must be less than EndDate
- Equality is based on the 3 properties together
- City has Taxes.
- City rejects duplicate Taxes (by object equality.)
- City rejects overlapping taxes (EndDate > other tax start date) for a given TaxType.
I first created tests for Tax, which validated that it was fed all properties from the constructor, did not allow nulls, based object equality on all 3 properties, and did not allow EndDate earlier than StartDate.
I then created tests for City, which added tax objects to Taxes collection, rejected duplicates, and rejected overlapping EndDates with StartDate by tax type.
Tests and classes below.
TaxTests.cs
[TestFixture]
public class TaxTests
{
[Test]
[ExpectedException(typeof(TaxValuesMissingException))]
public void TaxCannotBeCreatedWithAllNullProperties()
{
var tax = new Tax(null, null, null);
}
[Test]
[ExpectedException(typeof(TaxValuesMissingException))]
public void TaxCannotBeCreatedWithNullTaxType()
{
var tax = new Tax(null, DateTime.Today.AddDays(1), DateTime.Today.AddYears(1));
}
[Test]
[ExpectedException(typeof(TaxValuesMissingException))]
public void TaxCannotBeCreatedWithNullStartDate()
{
var tax = new Tax("PST", null, DateTime.Today.AddYears(1));
}
[Test]
[ExpectedException(typeof(TaxValuesMissingException))]
public void TaxCannotBeCreatedWithNullEndDate()
{
var tax = new Tax("PST", DateTime.Today.AddDays(1), null);
}
[Test]
public void TaxCanBeCreatedWhenAllPropertiesSupplied()
{
var tax = new Tax("PST", DateTime.Today.AddDays(1), DateTime.Today.AddYears(1));
Assert.IsNotNull(tax);
}
[Test]
[ExpectedException(typeof(InvalidTaxDateRangeException))]
public void TaxStartDateCannotBeGreaterThanEndDate()
{
var tax = new Tax("PST", DateTime.Today.AddYears(1).AddDays(1), DateTime.Today.AddYears(1));
Assert.IsNotNull(tax);
}
[Test]
public void TaxesAreEqualWhenConstructorParametersMatch()
{
var tax1 = new Tax("PST", DateTime.Today.AddDays(1), DateTime.Today.AddYears(1));
var tax2 = new Tax("PST", DateTime.Today.AddDays(1), DateTime.Today.AddYears(1));
Assert.IsTrue(tax1.Equals(tax2));
// Assert.AreSame(tax1, tax2) failed, may be referencing another nunit namespace??
}
}
Tax.cs
public class Tax
{
private readonly string _taxType;
private readonly DateTime? _startDate;
private readonly DateTime? _endDate;
private Tax()
{
}
public Tax(string taxType, DateTime? startDate, DateTime? endDate)
{
_taxType = taxType;
_startDate = startDate;
_endDate = endDate;
ValidateAllParametersHaveNonNullValue();
ValidateEndDateGreaterThanStartDate();
}
public string TaxType
{
get { return _taxType; }
}
public DateTime? StartDate
{
get { return _startDate; }
}
public DateTime? EndDate
{
get { return _endDate; }
}
public override bool Equals(object obj)
{
var otherTax = (Tax) obj;
var isEqual = otherTax.TaxType.Equals(this.TaxType)
&& otherTax.StartDate.Value.Equals(this.StartDate.Value)
&& otherTax.EndDate.Value.Equals(this.EndDate.Value);
return isEqual;
}
public override int GetHashCode()
{
return StartDate.GetHashCode() + EndDate.GetHashCode() + TaxType.GetHashCode();
}
private void ValidateEndDateGreaterThanStartDate()
{
if (StartDate.Value > EndDate.Value)
throw new InvalidTaxDateRangeException();
}
private void ValidateAllParametersHaveNonNullValue()
{
if (TaxType == null
|| !StartDate.HasValue
|| !EndDate.HasValue)
throw new TaxValuesMissingException();
}
}
CityTests.cs
[TestFixture]
public class CityTests
{
[Test]
public void CityCanAddTaxes()
{
var city = new City("Winnipeg");
var pstTax = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6));
city.AddTax(pstTax);
Assert.IsTrue(city.Taxes.Contains(pstTax));
}
[Test]
[ExpectedException(typeof(DuplicateTaxesException))]
public void CityRejectsDuplicateTaxes()
{
var city = new City("Winnipeg");
var pstTax1 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6));
var pstTax2 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6));
city.AddTax(pstTax1);
city.AddTax(pstTax2);
}
[Test]
[ExpectedException(typeof(OverlappingTaxTypesException))]
public void CityRejectsOverlappingTaxesPerTaxType()
{
var city = new City("Winnipeg");
var pstTax1 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6));
var pstTax2 = new Tax("PST", DateTime.Today.AddMonths(6), DateTime.Today.AddYears(1));
city.AddTax(pstTax1);
city.AddTax(pstTax2);
}
}
City.cs
public class City
{
private readonly string _name;
public City(string name)
{
_name = name;
Taxes = new List();
}
public List Taxes { get; private set; }
public void AddTax(Tax tax)
{
if (tax == null) throw new ArgumentNullException("tax");
RejectDuplicateTaxes(tax);
RejectOverlappingTaxes(tax);
this.Taxes.Add(tax);
}
private void RejectOverlappingTaxes(Tax tax)
{
foreach(var currowTax in this.Taxes)
{
if(IsFutureTax(tax, currowTax)
&& FutureTaxOverlapsEndDateOfCurrowTax(tax, currowTax))
throw new OverlappingTaxTypesException();
if(IsEarlierTax(tax, currowTax)
&& CurrowTaxOverlapsEndDateOfPreviousTax(tax, currowTax))
throw new OverlappingTaxTypesException();
}
}
private static bool CurrowTaxOverlapsEndDateOfPreviousTax(Tax tax, Tax currowTax)
{
return currowTax.TaxType.Equals(tax.TaxType)
&& currowTax.StartDate <= tax.EndDate; } private static bool IsEarlierTax(Tax tax, Tax currowTax) { return currowTax.TaxType.Equals(tax.TaxType) && tax.StartDate.Value < currowTax.StartDate; } private static bool FutureTaxOverlapsEndDateOfCurrowTax(Tax tax, Tax currowTax) { return currowTax.TaxType.Equals(tax.TaxType) && tax.StartDate <= currowTax.EndDate; } private static bool IsFutureTax(Tax tax, Tax currowTax) { return currowTax.TaxType.Equals(tax.TaxType) && tax.StartDate.Value > currowTax.StartDate;
}
private void RejectDuplicateTaxes(Tax tax)
{
if(this.Taxes.Contains(tax))
throw new DuplicateTaxesException();
}
}
No comments:
Post a Comment