Skip to content

Instantly share code, notes, and snippets.

@sviataslau
Created January 30, 2013 07:02
Show Gist options
  • Save sviataslau/4671317 to your computer and use it in GitHub Desktop.
Save sviataslau/4671317 to your computer and use it in GitHub Desktop.
Sample String Calculator TDD Kata 2 implementation. http://osherove.com/tdd-kata-2/ Based on Kata implementation from http://osherove.com/tdd-kata/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace StringCalculator
{
public class Calculator : ICalculator
{
private readonly IIOProvider _iio;
private static readonly Regex _inputParsingRegex = new Regex(@"(?<definition>//(?<delimiter>.+)\n).*");
private string _input;
public Calculator()
{
}
public Calculator(IIOProvider iio)
{
_iio = iio;
}
public int Add(string input)
{
if (input == null)
throw new ArgumentNullException("input");
if (input == string.Empty)
return 0;
_input = input;
var numbers = Parse();
EnsureNonNegatives(numbers);
int sum = numbers.Sum();
OutValue(sum);
return sum;
}
private void OutValue(int sum)
{
if (_iio != null)
_iio.Out(sum.ToString(CultureInfo.InvariantCulture));
}
private int[] Parse()
{
var delimiters = GetAllAllowedDelimiters();
var numbersInput = GetNumberInput().Split(delimiters).Where(n => !string.IsNullOrEmpty(n));
int[] numbers = Filter(numbersInput.Select(int.Parse).ToArray());
return numbers;
}
private static int[] Filter(IEnumerable<int> numbers)
{
return numbers.Where(n => n <= 1000).ToArray();
}
private static void EnsureNonNegatives(IEnumerable<int> numbers)
{
int[] negatives = numbers.Where(n => n < 0).ToArray();
if (negatives.Any())
{
string exceptionMessage = string.Format("negative not allowed:{0}", string.Join(",", negatives));
throw new ArgumentException(exceptionMessage);
}
}
private IEnumerable<string> GetUserDefinedDelimiters()
{
var match = _inputParsingRegex.Match(_input);
string delimiter = match.Groups["delimiter"].Value;
if (string.IsNullOrEmpty(delimiter))
return new string[0];
return delimiter.Split(new[] { '[', ']' });
}
private char[] GetAllAllowedDelimiters()
{
var delimiter = GetUserDefinedDelimiters();
return new[] { ',', '\n' }.Concat(delimiter.SelectMany(d => d.ToCharArray())).ToArray();
}
private string GetDelimiterDefinitionPart()
{
var match = _inputParsingRegex.Match(_input);
return match.Groups["definition"].Value;
}
private string GetNumberInput()
{
string delimiterDefinitionPart = GetDelimiterDefinitionPart();
return !string.IsNullOrEmpty(delimiterDefinitionPart) ? _input.Replace(delimiterDefinitionPart, string.Empty) : _input;
}
}
}
namespace StringCalculator
{
public interface ICalculator
{
int Add(string input);
}
}
namespace StringCalculator
{
public interface IIOProvider
{
void Out(string message);
string GetInput();
}
}
using System;
namespace StringCalculator.Tests
{
public class Program
{
private readonly ICalculator _calculator;
private readonly IIOProvider _iio;
public Program(ICalculator calculator, IIOProvider iio)
{
_calculator = calculator;
_iio = iio;
}
public void Execute()
{
while (true)
{
string input = _iio.GetInput();
if (input == Environment.NewLine)
break;
int result = _calculator.Add(input);
_iio.Out(string.Format("The result is {0}", result));
_iio.Out("another input please");
}
}
}
}
using System;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
namespace StringCalculator.Tests
{
public class Tests
{
private ICalculator _calculator;
private IIOProvider _io;
[Test]
public void should_return_sum_for_empty()
{
var calculator = new Calculator();
int result = calculator.Add("");
result.Should().Be(0);
}
[Test]
public void should_return_sum_for_one_number()
{
var calculator = new Calculator();
int result = calculator.Add("10");
result.Should().Be(10);
}
[Test]
public void should_return_sum_for_two_numbers()
{
var calculator = new Calculator();
int result = calculator.Add("10,11");
result.Should().Be(21);
}
[Test]
public void should_return_sum_for_three_numbers()
{
var calculator = new Calculator();
int result = calculator.Add("10,11,12");
result.Should().Be(33);
}
[Test]
public void should_return_sum_for_unknown_amount_of_numbers()
{
var calculator = new Calculator();
int result = calculator.Add("1,2,3,4,5");
result.Should().Be(15);
}
[Test]
public void should_return_sum_both_with_command_and_newline_separators()
{
var calculator = new Calculator();
int result = calculator.Add("1\n2,3");
result.Should().Be(6);
}
[Test]
public void should_return_sum_with_custom_delimiter()
{
var calculator = new Calculator();
int result = calculator.Add("//;\n1;2");
result.Should().Be(3);
}
[Test]
public void should_throw_exception_if_negative_numbers_are_passed()
{
var calculator = new Calculator();
Action call = () => calculator.Add("-1,10,-2");
call.ShouldThrow<ArgumentException>("negative not allowed:-1,-2");
}
[Test]
public void should_ignore_numbers_greater_than_1000()
{
var calculator = new Calculator();
int result = calculator.Add("10,2,1001");
result.Should().Be(12);
}
[Test]
public void should_return_sum_with_custom_delimiter_with_any_length()
{
var calculator = new Calculator();
int result = calculator.Add("//***\n1***2***3");
result.Should().Be(6);
}
[Test]
public void should_return_sum_with_more_than_one_custom_delimiter()
{
var calculator = new Calculator();
int result = calculator.Add("//[*][%]\n1*2%3");
result.Should().Be(6);
}
[Test]
public void should_return_sum_with_more_than_one_custom_delimiter_with_any_length()
{
var calculator = new Calculator();
int result = calculator.Add("//[***][%]\n1***2%3");
result.Should().Be(6);
}
[Test]
public void should_return_sum_wht_both_custom_and_standard_delimiters()
{
var calculator = new Calculator();
int result = calculator.Add("//[***][%]\n1***2,3%4\n5");
result.Should().Be(15);
}
[Test]
public void should_output_sum_to_console()
{
var output = Substitute.For<IIOProvider>();
var calculator = new Calculator(output);
calculator.Add("1,2");
output.Received().Out("3");
}
[Test]
public void exec_should_out_to_contole()
{
var program = new Program(_calculator, _io);
program.Execute();
_io.Received().Out("The result is 6");
}
[Test]
public void exec_should_ask_for_next_input()
{
var program = new Program(_calculator, _io);
program.Execute();
_io.Received().Out("The result is 6");
_io.Received().Out("another input please");
}
[Test]
public void exec_should_wait_for_next_input()
{
_calculator.Add("4,5,6").Returns(15);
_calculator.Add("7,8,9").Returns(24);
_io.GetInput().Returns(x => "1,2,3", x => "4,5,6", x => "7,8,9", x => Environment.NewLine);
var program = new Program(_calculator, _io);
program.Execute();
_io.Received().Out("The result is 6");
_io.Received().Out("another input please");
_io.Received().Out("The result is 15");
_io.Received().Out("another input please");
_io.Received().Out("The result is 24");
_io.Received().Out("another input please");
}
[SetUp]
public void Setup()
{
_calculator = Substitute.For<ICalculator>();
_calculator.Add("1,2,3").Returns(6);
_io = Substitute.For<IIOProvider>();
_io.GetInput().Returns(x => "1,2,3", x => Environment.NewLine);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment