Recently, during the interview, I was asked to implement basic arithmetic operations with common fractions using TDD. The task is fairly simple, but I was nervous so I did not do too well. :) Later on I decided to give it another try in more... comfortable environment. Here is what I came up with.
Tests:
[TestFixture]
class FractionTests
{
[Test]
public void NormalizationTest()
{
var fraction = new Fraction(24, 36).Normalize();
Assert.That(fraction.Numerator, Is.EqualTo(2));
Assert.That(fraction.Denominator, Is.EqualTo(3));
}
[Test]
public void EqualityTest()
{
Assert.That(new Fraction(1, 2), Is.EqualTo(new Fraction(1, 2)));
Assert.That(new Fraction(5, 10), Is.EqualTo(new Fraction(1, 2)));
}
[Test]
public void ComparisonTest()
{
Assert.That(new Fraction(5, 10).CompareTo(new Fraction(1, 2)), Is.EqualTo(0));
Assert.That(new Fraction(5, 10).CompareTo(new Fraction(9, 4)), Is.EqualTo(-1));
Assert.That(new Fraction(5, 10).CompareTo(new Fraction(1, 5)), Is.EqualTo(1));
}
[Test]
public void AdditionTestWithSameDenominators()
{
var fraction1 = new Fraction(1,5);
var fraction2 = new Fraction(7,5);
var result = fraction1.Add(fraction2);
Assert.That(result, Is.EqualTo(new Fraction(8, 5)));
}
[Test]
public void AdditionTestWithDifferentDenominators()
{
var fraction1 = new Fraction(1, 25);
var fraction2 = new Fraction(6, 15);
var result = fraction1.Add(fraction2);
Assert.That(result, Is.EqualTo(new Fraction(33, 75)));
}
[Test]
public void SubtractionTestWithSameDenominators()
{
var fraction1 = new Fraction(1, 5);
var fraction2 = new Fraction(7, 5);
var result = fraction1.Subtract(fraction2);
Assert.That(result, Is.EqualTo(new Fraction(-6, 5)));
}
[Test]
public void SubtractionTestWithDifferentDenominators()
{
var fraction1 = new Fraction(1, 25);
var fraction2 = new Fraction(6, 15);
var result = fraction2.Subtract(fraction1);
Assert.That(result, Is.EqualTo(new Fraction(27, 75)));
}
[Test]
public void MultiplicationTest()
{
var fraction1 = new Fraction(2, 3);
var fraction2 = new Fraction(3, 5);
var result = fraction1.Multiply(fraction2);
Assert.That(result, Is.EqualTo(new Fraction(6, 15)));
}
[Test]
public void DivisionTest()
{
var fraction1 = new Fraction(2, 3);
var fraction2 = new Fraction(3, 5);
var result = fraction2.Divide(fraction1);
Assert.That(result, Is.EqualTo(new Fraction(9, 10)));
}
[Test]
public void TestZeroHandling()
{
var fraction = new Fraction(2,3);
var zero = new Fraction(0, 29);
Assert.That(fraction.Add(zero), Is.EqualTo(fraction));
Assert.That(fraction.Subtract(zero), Is.EqualTo(fraction));
Assert.That(fraction.Multiply(zero), Is.EqualTo(zero));
Assert.That(() => fraction.Divide(zero), Throws.TypeOf<DivideByZeroException>());
}
}
I am uncertain, if those tests are good enough. I am also fairly new to TDD, so any advice is welcome.
Fraction class:
public class Fraction : IEquatable<Fraction>, IComparable<Fraction>
{
public Fraction(int numerator = 0, int denominator = 1)
{
if (denominator < 1) throw new ArgumentOutOfRangeException("denominator", "Denominator should be greater than 0");
Numerator = numerator;
Denominator = denominator;
}
public int Numerator { get; private set; }
public int Denominator { get; private set; }
public Fraction Add(Fraction other)
{
if (other == null) throw new ArgumentNullException("other");
Fraction result;
if (other.Denominator == Denominator)
{
result = new Fraction(Numerator + other.Numerator, Denominator);
}
else
{
var commonDenominator = Denominator*other.Denominator;
var numerator = Denominator*other.Numerator + other.Denominator*Numerator;
result = new Fraction(numerator, commonDenominator);
}
return result.Normalize();
}
public Fraction Subtract(Fraction other)
{
if (other == null) throw new ArgumentNullException("other");
return Add(new Fraction(-other.Numerator, other.Denominator));
}
public Fraction Multiply(Fraction other)
{
if (other == null) throw new ArgumentNullException("other");
return new Fraction(Numerator * other.Numerator, Denominator * other.Denominator).Normalize();
}
public Fraction Divide(Fraction other)
{
if (other == null) throw new ArgumentNullException("other");
if (other.Numerator == 0) throw new DivideByZeroException();
return Multiply(new Fraction(other.Denominator, other.Numerator));
}
public Fraction Normalize()
{
var commonDivisor = FindCommonDivisor(Math.Abs(Numerator), Math.Abs(Denominator));
return new Fraction(Numerator / commonDivisor, Denominator / commonDivisor);
}
public bool Equals(Fraction other)
{
if (other == null) return false;
var first = Normalize();
var second = other.Normalize();
return first.Numerator == second.Numerator && first.Denominator == second.Denominator;
}
public override bool Equals(object obj)
{
return Equals(obj as Fraction);
}
public override int GetHashCode()
{
unchecked
{
var normalized = Normalize();
return (normalized.Numerator * 397) ^ normalized.Denominator;
}
}
public int CompareTo(Fraction other)
{
return ToDouble().CompareTo(other.ToDouble());
}
public override string ToString()
{
return String.Format("{0}/{1}", Numerator, Denominator);
}
public double ToDouble()
{
return (double)Numerator/Denominator;
}
private static int FindCommonDivisor(int first, int second)
{
if (second == 0)
{
return first;
}
var mod = first % second;
return FindCommonDivisor(second, mod);
}
}
Anything to improve?