[ChatGPT-4] XUnit Framework로 하는 .NET Test Development Driven(TDD) 을 물어봤습니다.

Q. .NET의 Unit Test Framework 중 하나인, XUnit 에 대해서 설명해주고, Test Development Driven의 주요한 이론들을 적용했을 때 어떤 C# 코드가 나와야하는지 예제로 보여줘

A. XUnit은 .NET 개발 환경에서 널리 사용되는 Unit Test Framework 중 하나입니다. XUnit은 테스트 케이스를 작성하기 위한 풍부한 기능과 함께, 개발자가 테스트를 더 쉽게 구성하고 실행할 수 있는 도구를 제공합니다. XUnit의 주요 특징은 다음과 같습니다.

  1. 가독성이 좋은 테스트 케이스 작성: XUnit은 테스트 케이스 작성을 위한 애트리뷰트(Attributes)와 메서드 이름을 사용하여, 코드의 가독성과 유지 보수성을 높입니다.
  2. 테스트 격리: 각 테스트는 서로 독립적으로 실행되며, 테스트 간의 상태 공유를 방지합니다.
  3. 데이터 주도 테스트: InlineData, MemberData, ClassData 등의 애트리뷰트를 사용하여 다양한 입력 데이터를 제공할 수 있습니다.

Test-Driven Development (TDD)는 개발 과정에서 테스트 케이스를 먼저 작성하고, 이를 통과하는 코드를 작성하는 방법론입니다. TDD의 주요 단계는 다음과 같습니다.

  1. 실패하는 테스트 케이스 작성: 기능 요구 사항을 충족하는 테스트 케이스를 먼저 작성합니다.
  2. 테스트를 통과하는 코드 작성: 테스트 케이스를 통과할 수 있는 최소한의 코드를 작성합니다.
  3. 코드 리팩토링: 코드를 개선하고 중복을 제거하여 가독성과 유지 보수성을 높입니다.

다음은 TDD를 적용하여 작성된 C# 코드 예제입니다.

  1. 먼저, XUnit을 설치하고 프로젝트에 추가합니다.
dotnet add package Xunit
dotnet add package Xunit.runner.visualstudio
  1. 테스트 클래스를 작성합니다.
using Xunit;
using MyApp;

namespace MyApp.Tests
{
    public class CalculatorTests
    {
        [Fact]
        public void Add_ReturnsSum()
        {
            // Arrange
            var calculator = new Calculator();
            int a = 5;
            int b = 7;

            // Act
            int result = calculator.Add(a, b);

            // Assert
            Assert.Equal(12, result);
        }
    }
}
  1. 기능을 구현할 클래스를 작성합니다.
namespace MyApp
{
    public class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
}

이 예제에서는 간단한 계산기 애플리케이션을 만들고, 덧셈 기능을 테스트하는 테스트 케이스를 작성했습니다. 테스트 케이스가 성공적으로 통과한 후에 다른 기능을 추가하고, 해당 기능에 대한 테스트 케이스를 작성할 수 있습니다. 예를 들어, 계산기에 뺄셈, 곱셈, 나눗셈 기능을 추가하고 이를 테스트하는 코드를 작성해 보겠습니다.

  1. 뺄셈, 곱셈, 나눗셈 기능을 포함하는 테스트 케이스를 작성합니다.
using Xunit;
using MyApp;

namespace MyApp.Tests
{
    public class CalculatorTests
    {
        // ... 기존 Add_ReturnsSum 테스트 케이스

        [Fact]
        public void Subtract_ReturnsDifference()
        {
            // Arrange
            var calculator = new Calculator();
            int a = 10;
            int b = 6;

            // Act
            int result = calculator.Subtract(a, b);

            // Assert
            Assert.Equal(4, result);
        }

        [Fact]
        public void Multiply_ReturnsProduct()
        {
            // Arrange
            var calculator = new Calculator();
            int a = 3;
            int b = 5;

            // Act
            int result = calculator.Multiply(a, b);

            // Assert
            Assert.Equal(15, result);
        }

        [Fact]
        public void Divide_ReturnsQuotient()
        {
            // Arrange
            var calculator = new Calculator();
            int a = 20;
            int b = 4;

            // Act
            int result = calculator.Divide(a, b);

            // Assert
            Assert.Equal(5, result);
        }
    }
}
  1. 계산기 클래스에 뺄셈, 곱셈, 나눗셈 기능을 추가합니다.
namespace MyApp
{
    public class Calculator
    {
        // ... 기존 Add 메서드

        public int Subtract(int a, int b)
        {
            return a - b;
        }

        public int Multiply(int a, int b)
        {
            return a * b;
        }

        public int Divide(int a, int b)
        {
            return a / b;
        }
    }
}

이제 각각의 새로운 기능에 대한 테스트 케이스가 추가되었고, 계산기 클래스에 뺄셈, 곱셈, 나눗셈 기능이 구현되었습니다. 이렇게 TDD를 적용하면, 새로운 기능을 추가하거나 기존 기능을 수정할 때 테스트 케이스를 먼저 작성하고, 이를 통과하는 코드를 작성함으로써 개발 과정에서 발생할 수 있는 오류를 줄이고 코드의 안정성을 높일 수 있습니다.

3개의 좋아요

Q. 그럼 TDD에서 주로 사용되는 용어들에 대해서 알려줘

A. TDD (Test-Driven Development)에서 주로 사용되는 용어들은 다음과 같습니다.

  1. 테스트 케이스 (Test Case): 특정 기능이나 요구 사항을 검증하기 위한 테스트 조건 및 절차를 포함한 단위입니다. 테스트 케이스는 일반적으로 하나의 기능이나 시나리오에 대한 입력 값과 예상 결과를 포함합니다.
  2. 테스트 스위트 (Test Suite): 관련된 테스트 케이스들의 모음입니다. 테스트 스위트는 일반적으로 하나의 모듈, 컴포넌트, 클래스 또는 기능에 대한 여러 테스트 케이스를 그룹화하여 구성됩니다.
  3. 테스트 픽스처 (Test Fixture): 테스트 케이스를 실행하기 전에 필요한 준비 작업을 수행하거나, 테스트 후에 정리 작업을 수행하는 코드입니다. 예를 들어, 데이터베이스 연결을 설정하거나 테스트용 파일을 생성하는 작업이 포함될 수 있습니다.
  4. 테스트 더블 (Test Double): 실제 객체를 대신하여 테스트에서 사용되는 객체로, 테스트를 더 쉽게 작성하고 실행할 수 있게 합니다. 테스트 더블의 종류로는 스텁 (Stub), 모의 객체 (Mock), 스파이 (Spy), 더미 (Dummy) 등이 있습니다.
  5. 단위 테스트 (Unit Test): 소프트웨어의 작은 부분 (일반적으로 하나의 함수 또는 메서드)에 대한 테스트입니다. 단위 테스트는 해당 부분의 독립적인 기능을 검증하고, 기대하는 대로 작동하는지 확인합니다.
  6. 통합 테스트 (Integration Test): 여러 소프트웨어 컴포넌트 또는 시스템 간의 상호 작용을 검증하는 테스트입니다. 통합 테스트는 컴포넌트 간의 인터페이스와 상호 작용이 올바르게 수행되는지 확인합니다.
  7. 회귀 테스트 (Regression Test): 변경된 코드가 기존 기능에 영향을 주지 않았는지 확인하기 위해 수행하는 테스트입니다. 회귀 테스트는 소프트웨어 수정 후에 발생할 수 있는 예기치 않은 오류를 찾아내고, 기존 기능의 안정성을 유지하는 데 도움이 됩니다.
  8. 테스트 주도 개발 (TDD)의 주요 원칙인 빨간-초록-파란 (Red-Green-Blue) 사이클은 다음과 같습니다.
  • 빨간 (Red): 실패하는 테스트 케이스를 작성합니다. 이 단계에서는 구현되지 않은 기능이나 수정되어야 할 버그 등에 대한 테스트 케이스를 작성하고, 테스트가 실패하는 것을 확인합니다.
  • 초록 (Green): 실패한 테스트를 통과하도록 최소한의 코드를 작성합니다. 이 단계에서는 테스트를 통과할 수 있는 간단한 코드를 작성하며, 코드의 품질에 대한 고려는 일단 미룹니다.
  • 파란 (Blue): 코드를 리팩토링하고 개선합니다. 테스트가 통과한 상태에서 코드의 가독성, 유지 보수성, 중복 제거 등을 고려하여 코드를 최적화합니다.
  1. 코드 커버리지 (Code Coverage): 테스트 케이스가 소프트웨어의 어느 정도의 비율을 검증하고 있는지를 나타내는 지표입니다. 일반적으로 코드의 라인, 분기, 함수 등에 대한 커버리지를 측정하며, 높은 코드 커버리지는 테스트의 완전성을 나타낼 수 있지만, 반드시 테스트의 품질이 높다는 것을 보장하지는 않습니다.
  2. 리팩토링 (Refactoring): 코드를 개선하고 정리하는 과정으로, 기능적인 변경 없이 코드의 가독성, 유지 보수성, 중복 제거 등을 개선합니다. TDD에서는 테스트를 통과한 코드를 리팩토링하여 개발 과정에서 발생할 수 있는 기술 부채를 줄이고, 코드의 품질을 높입니다.

이러한 용어들은 TDD에서 사용되는 핵심 개념들로, 테스트 주도 개발을 이해하고 구현하는 데 필요한 지식을 제공합니다. TDD를 통해 개발자들은 높은 품질의 코드를 작성할 수 있으며, 안정적이고 유지 보수가 쉬운 소프트웨어를 구축할 수 있습니다.

2개의 좋아요

Q. XUnit으로 Test Fixture에 대한 부분이 어떤지 코드로 보여줘

A. XUnit에서는 Test Fixture를 구현하기 위해 생성자(Constructor)와 IDisposable 인터페이스를 사용합니다. 생성자를 통해 테스트 시작 전에 필요한 설정을 할 수 있고, IDisposable 인터페이스의 Dispose 메서드를 통해 테스트 종료 후에 정리 작업을 수행할 수 있습니다.

다음은 XUnit을 사용하여 Test Fixture를 구현하는 예제입니다.

테스트 클래스에 생성자와 IDisposable 인터페이스를 구현합니다.

using System;
using Xunit;
using MyApp;

namespace MyApp.Tests
{
    public class CalculatorTestFixture : IDisposable
    {
        public Calculator Calculator { get; private set; }

        public CalculatorTestFixture()
        {
            // 테스트 시작 전에 필요한 설정 작업을 수행합니다.
            Calculator = new Calculator();
            Console.WriteLine("Calculator 생성");
        }

        public void Dispose()
        {
            // 테스트 종료 후에 정리 작업을 수행합니다.
            Calculator = null;
            Console.WriteLine("Calculator 제거");
        }
    }

    // Test Fixture를 사용하는 테스트 클래스입니다.
    public class CalculatorTests : IClassFixture<CalculatorTestFixture>
    {
        private Calculator _calculator;

        public CalculatorTests(CalculatorTestFixture fixture)
        {
            _calculator = fixture.Calculator;
        }

        [Fact]
        public void Add_ReturnsSum()
        {
            // Arrange
            int a = 5;
            int b = 7;

            // Act
            int result = _calculator.Add(a, b);

            // Assert
            Assert.Equal(12, result);
        }

        // 다른 테스트 메서드들...
    }
}

이 예제에서는 CalculatorTestFixture 클래스를 사용하여 테스트 시작 전에 Calculator 인스턴스를 생성하고, 테스트 종료 후에 해당 인스턴스를 제거하는 Test Fixture를 구현했습니다. 테스트 클래스에서 IClassFixture 인터페이스를 구현하고, 생성자에서 Test Fixture 인스턴스를 전달받아 테스트 메서드에서 사용할 수 있습니다.

Test Fixture를 사용하면 테스트 환경을 관리하고 정리하는 작업을 쉽게 할 수 있으며, 테스트 실행 시간을 줄이고 리소스를 효율적으로 활용할 수 있습니다.

1개의 좋아요

Q. XUnit으로 Test Double에 대한 부분이 어떤지 코드로 예시를 들어줘

A. XUnit에서 Test Double을 사용하는 경우, 대표적으로 Moq 라이브러리를 사용하여 Mock 객체를 생성하고 관리합니다. Moq는 C#에서 널리 사용되는 mocking 라이브러리로, 테스트에서 실제 객체 대신 사용할 수 있는 Mock 객체를 손쉽게 생성할 수 있습니다.

다음은 XUnit과 Moq를 사용하여 Test Double을 구현하는 예제입니다.

  1. 프로젝트에 Moq 라이브러리를 추가합니다. NuGet 패키지 관리자에서 Moq를 검색하여 설치하거나, 패키지 관리자 콘솔에서 다음 명령어를 실행합니다.
Install-Package Moq
  1. 의존성 주입을 사용하는 인터페이스와 클래스를 정의합니다.
// IRepository 인터페이스
public interface IRepository
{
    int GetData(int id);
}

// MyClass 클래스
public class MyClass
{
    private IRepository _repository;

    public MyClass(IRepository repository)
    {
        _repository = repository;
    }

    public int ProcessData(int id)
    {
        int data = _repository.GetData(id);
        return data * 2;
    }
}
  1. XUnit과 Moq를 사용하여 MyClass의 ProcessData 메서드를 테스트하는 테스트 케이스를 작성합니다.
using Xunit;
using Moq;
using MyApp;

namespace MyApp.Tests
{
    public class MyClassTests
    {
        [Fact]
        public void ProcessData_ReturnsDoubledData()
        {
            // Arrange
            int id = 1;
            int data = 10;
            int expected = data * 2;

            var mockRepository = new Mock<IRepository>();
            mockRepository.Setup(repo => repo.GetData(id)).Returns(data);

            var myClass = new MyClass(mockRepository.Object);

            // Act
            int result = myClass.ProcessData(id);

            // Assert
            Assert.Equal(expected, result);
            mockRepository.Verify(repo => repo.GetData(id), Times.Once());
        }
    }
}

위 예제에서는 IRepository 인터페이스의 Mock 객체를 생성하고, GetData 메서드가 호출될 때 반환할 값을 설정합니다. 이렇게 생성된 Mock 객체를 MyClass에 주입하고, ProcessData 메서드를 테스트합니다. 결과를 검증하는 과정에서 Mock 객체의 GetData 메서드가 한 번 호출되었는지 확인할 수 있습니다.

Test Double을 사용하면 테스트 대상이 의존하는 외부 객체나 서비스를 쉽게 대체할 수 있으며, 테스트의 격리와 복잡성을 줄여서 테스트 작성과 실행이 더 쉬워집니다.

1개의 좋아요

Q. 위에 예시로 들어준 코드에 아까 위에서 설명한 Test Double의 Fake, Dummy, Stub, Spy, Mock의 개념이 모두 들어 있는거야?

A. 위 예제 코드는 주로 Mock 개념을 사용하고 있습니다. 다른 Test Double 개념들을 명확하게 설명하기 위해 각각의 개념에 대한 간단한 설명과 예제를 추가로 제공하겠습니다.

  1. Dummy: 테스트에 사용되지 않지만, 테스트 대상의 의존성을 해결하기 위해 필요한 객체입니다. 주로 인자로 전달되지만, 테스트에서 실제로 사용되지 않습니다.
public class DummyLogger : ILogger
{
    public void Log(string message) {}
}

[Fact]
public void TestMethod()
{
    var dummyLogger = new DummyLogger();
    var myClass = new MyClass(dummyLogger);
    // ...
}
  1. Stub: 테스트에서 사용될 객체의 메서드에 대한 고정된 반환 값을 제공하는 객체입니다. 테스트를 단순화하고 테스트의 목적에 따라 반환 값을 설정할 수 있습니다.
public class StubRepository : IRepository
{
    public int GetData(int id)
    {
        return 42; // 항상 42를 반환하는 Stub 메서드
    }
}

[Fact]
public void TestMethod()
{
    var stubRepository = new StubRepository();
    var myClass = new MyClass(stubRepository);
    // ...
}
  1. Spy: 테스트에서 호출된 메서드와 인자에 대한 정보를 기록하는 객체입니다. 테스트 후에 이 정보를 사용하여 메서드 호출 여부와 인자 값을 검증할 수 있습니다.
public class SpyLogger : ILogger
{
    public List<string> LoggedMessages { get; } = new List<string>();

    public void Log(string message)
    {
        LoggedMessages.Add(message);
    }
}

[Fact]
public void TestMethod()
{
    var spyLogger = new SpyLogger();
    var myClass = new MyClass(spyLogger);
    // ...

    Assert.Contains("Expected log message", spyLogger.LoggedMessages);
}
  1. Mock: 테스트에서 사용될 객체의 메서드에 대한 반환 값을 설정하고, 메서드 호출 여부 및 인자 값을 검증하는 객체입니다. 위의 예제 코드에서 사용한 Moq 라이브러리를 통해 생성한 Mock 객체가 이에 해당합니다.
  2. Fake: 실제 로직이 구현되었지만, 단순화된 버전이나 테스트용 구현을 제공하는 객체입니다. 테스트 환경에서 실제 객체 대신 사용하여 테스트의 복잡성을 줄일 수 있습니다.
public class FakeRepository : IRepository
{
    public int GetData(int id)
    {
        // 단순한 구현을 사용하여 테스트용 데이터를 반환
        return id * 10;
    }
}

[Fact]
public void TestMethod()
{
    var fakeRepository = new FakeRepository();
    var myClass = new MyClass(fakeRepository);
    // ...
}

위에서 설명한 예제들은 각 Test Double 개념에 따라 작성된 코드입니다. 이러한 Test Double 기법들은 테스트를 더 쉽게 작성하고 검증하도록 도와줍니다. 이러한 기법들은 서로 독립적이기도 하지만, 경우에 따라서 함께 사용될 수도 있습니다. 예를 들어, Mock 객체를 사용하여 Stub과 Spy의 기능을 모두 구현할 수 있습니다.

테스트 환경에서 의존성이 있는 객체를 대체하는 방식을 선택할 때, 테스트의 목적과 필요성에 따라 적절한 Test Double 기법을 사용하면 좋습니다. 이를 통해 테스트의 복잡성을 줄이고, 테스트 대상에 집중하여 견고한 테스트 케이스를 작성할 수 있습니다.

다양한 Test Double 기법들을 이해하고 사용하는 것은 테스트 주도 개발 및 소프트웨어 품질 향상에 큰 도움이 됩니다. 테스트 대상과 상황에 따라 적절한 Test Double 기법을 선택하여 효과적인 테스트를 구현하실 수 있기를 바랍니다.

1개의 좋아요

Q. InlineData로 Stub을 정의할 때 성공하는 케이스와 실패하는 케이스를 같은 테스트 메서드에서 구현해야 맞는거야?

A. InlineData를 사용하면 여러 입력 값과 예상되는 결과를 동일한 테스트 메서드에 대해 제공할 수 있습니다. 그러나 성공하는 케이스와 실패하는 케이스를 결정하는 것은 테스트 메서드의 목적과 상황에 따라 다릅니다.

일반적으로, 성공 케이스와 실패 케이스는 별도의 테스트 메서드로 구현하는 것이 좋습니다. 이렇게 하면 각 테스트 메서드가 하나의 명확한 목적을 가지게 되며, 테스트 실패 시 원인을 빠르게 찾아낼 수 있습니다. 그러나 경우에 따라 성공 케이스와 실패 케이스를 동일한 테스트 메서드에서 처리하는 것이 효율적일 수도 있습니다.

Stub을 사용한 테스트 케이스에서 InlineData를 사용한 예를 들겠습니다. 우선 인터페이스와 Stub 구현체를 작성합니다.

public interface ICalculator
{
    int Divide(int dividend, int divisor);
}

public class CalculatorStub : ICalculator
{
    public int Divide(int dividend, int divisor)
    {
        if (divisor == 0)
        {
            throw new DivideByZeroException();
        }
        return dividend / divisor;
    }
}

InlineData를 사용하여 성공 케이스와 실패 케이스를 동일한 테스트 메서드에서 처리하는 예제입니다.

using Xunit;
using MyApp;

namespace MyApp.Tests
{
    public class CalculatorTests
    {
        private readonly ICalculator _calculator;

        public CalculatorTests()
        {
            _calculator = new CalculatorStub();
        }

        [Theory]
        [InlineData(10, 2, 5, null)]
        [InlineData(9, 3, 3, null)]
        [InlineData(8, 0, 0, typeof(DivideByZeroException))]
        public void Divide_TestCases(int dividend, int divisor, int expected, Type expectedExceptionType)
        {
            if (expectedExceptionType == null)
            {
                int result = _calculator.Divide(dividend, divisor);
                Assert.Equal(expected, result);
            }
            else
            {
                Assert.Throws(expectedExceptionType, () => _calculator.Divide(dividend, divisor));
            }
        }
    }
}

위 예제에서는 성공하는 경우와 예외가 발생하는 경우를 InlineData를 통해 제공하고 있습니다. 테스트 메서드는 예상되는 예외 유형에 따라 결과를 검증하거나 예외를 확인합니다.

이러한 방식으로 성공 케이스와 실패 케이스를 동일한 테스트 메서드에서 처리할 수 있지만, 각각의 테스트 케이스가 하나의 명확한 목적을 가지도록 분리하는 것이 좋습니다. 이렇게 하면 테스트 결과를 더 명확하게 이해할 수 있으며, 테스트 실패 시 문제를 더 쉽게 해결할 수 있습니다. 성공 케이스와 실패 케이스를 별도의 테스트 메서드로 구현하는 것이 좋습니다.

using Xunit;
using MyApp;

namespace MyApp.Tests
{
    public class CalculatorTests
    {
        private readonly ICalculator _calculator;

        public CalculatorTests()
        {
            _calculator = new CalculatorStub();
        }

        [Theory]
        [InlineData(10, 2, 5)]
        [InlineData(9, 3, 3)]
        public void Divide_ValidInputs_ReturnsCorrectResult(int dividend, int divisor, int expected)
        {
            int result = _calculator.Divide(dividend, divisor);
            Assert.Equal(expected, result);
        }

        [Theory]
        [InlineData(8, 0)]
        public void Divide_DivideByZero_ThrowsDivideByZeroException(int dividend, int divisor)
        {
            Assert.Throws<DivideByZeroException>(() => _calculator.Divide(dividend, divisor));
        }
    }
}

위 예제에서는 성공 케이스와 실패 케이스를 별도의 테스트 메서드로 구현하였습니다. 이렇게 하면 테스트 결과를 더 명확하게 이해할 수 있으며, 각 테스트 케이스가 하나의 명확한 목적을 가집니다. 또한 테스트 실패 시 문제를 더 쉽게 해결할 수 있습니다.

요약하자면, InlineData를 사용할 때 성공 케이스와 실패 케이스를 구분하여 각각의 테스트 메서드에서 처리하는 것이 좋습니다. 이렇게 하면 테스트의 목적이 명확해지며, 테스트 결과를 이해하기 쉽고 문제를 빠르게 해결할 수 있습니다.

1개의 좋아요

Q. 앞서 설명한 Red Green Blue 의 순서도 코드를 통해 예시를 보여줄 수 있어?

A. 물론입니다! Red-Green-Blue(TDD 사이클)에 대한 간단한 예제를 드리겠습니다. 다음과 같은 요구사항을 가진 간단한 문자열 계산기를 만들려고 합니다.

  1. 빈 문자열 또는 null이 주어지면 0을 반환합니다.
  2. 하나의 숫자가 주어지면 숫자를 반환합니다.
  3. 쉼표로 구분된 두 개의 숫자가 주어지면 두 숫자의 합을 반환합니다.

Red

첫 번째 단계는 실패하는 테스트 작성입니다. 문자열 계산기에 대한 테스트 케이스를 작성합니다.

using Xunit;

namespace StringCalculator.Tests
{
    public class StringCalculatorTests
    {
        [Theory]
        [InlineData("", 0)]
        [InlineData(null, 0)]
        [InlineData("1", 1)]
        [InlineData("1,2", 3)]
        public void Add_GivenString_ReturnsSum(string input, int expected)
        {
            // Arrange
            var calculator = new StringCalculator();

            // Act
            int result = calculator.Add(input);

            // Assert
            Assert.Equal(expected, result);
        }
    }
}

Green

두 번째 단계는 테스트를 통과할 수 있는 최소한의 코드 작성입니다. 문자열 계산기를 구현합니다.

public class StringCalculator
{
    public int Add(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return 0;
        }

        string[] numbers = input.Split(',');

        if (numbers.Length == 1)
        {
            return int.Parse(numbers[0]);
        }

        return int.Parse(numbers[0]) + int.Parse(numbers[1]);
    }
}

Blue (Refactor)

마지막 단계는 코드를 개선하고 리팩토링하는 것입니다. 현재 코드는 문제를 해결하지만, 다음과 같이 개선할 수 있습니다.

public class StringCalculator
{
    public int Add(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return 0;
        }

        string[] numbers = input.Split(',');
        return numbers.Sum(number => int.Parse(number));
    }
}

코드를 개선했으니, 테스트를 다시 실행하여 여전히 모든 테스트 케이스가 통과하는지 확인합니다.

이것으로 Red-Green-Blue 사이클이 완료되었습니다. 이제 새로운 요구사항이나 기능이 추가되면 해당 사이클을 반복하여 개발을 진행할 수 있습니다.

4개의 좋아요