도메인 지식이 단독 테스트에 몰래 들어가는 것을 방지

이전에 논의했습니다why solitary tests should be easy to read. 때때로 단독 테스트의 가독성은 사물을 지나치게 복잡하게 하거나 엔지니어링하는 개발자에 의해 영향을 받습니다. 의심할 여지 없이 의도는 좋았지만 결국에는 상당히 해로웠습니다. 복잡한 단독 테스트는 팀의 다른 구성원에게 심각한 두통을 유발할 수 있습니다.

이에 대한 한 가지 예는 때때로 나타나는 문제입니다. 도메인 로직이 단독 테스트 구현에 잠입한 경우입니다. 이것은 도메인 개체에서 알고리즘이나 비즈니스 논리를 실행하는 단독 테스트에서 가장 자주 발생하는 것 같습니다.

실제 사례를 살펴보겠습니다.

public class SolarPanelInstallation
{
    public IEnumerable<SolarPanel> SolarPanels { get; }

    public SolarPanelInstallation(IEnumerable<SolarPanel> solarPanels)
    {
        SolarPanels = solarPanels;
    }

    public Watts CalculateTheoreticalCapacity()
    {
        return SolarPanels.Aggregate(Watts.Of(0), (accumulator, solarPanel) 
            => accumulator + solarPanel.Capacity);
    }
}

public class SolarPanel
{
    public Watts Capacity { get; }

    public SolarPanel(Watts capacity)
    {
        Capacity = capacity;
    }
}

public readonly struct Watts
{
    public int Value { get; }

    private Watts(int value)
    {
        Value = value;
    }

    public static Watts Of(int value)
    {
        return new Watts(value);
    }

    public static Watts operator +(Watts a, Watts b)
    {
        return Of(a.Value + b.Value);
    }

    public override string ToString()
    {
        return $"{Value} Watts";
    }
}



여기에서 우리는 태양 전지판을 사용하여 녹색 에너지를 생성하는 영역에 진입했습니다. 태양 전지판 설치는 하나 또는 여러 개의 태양 전지판으로 구성될 수 있습니다. 각 태양 전지판에는 와트로 표시되는 자체 용량이 있습니다. SolarPanelInstallation 클래스는 전체 설치의 이론적 용량을 계산하는 방법을 제공합니다.

테스트 코드를 살펴보자.

[Specification]
public class When_calculating_the_theoretical_capacity_of_a_solar_panels_installation
{
    [Establish]
    public void Context()
    {
        var solarPanels = new[]
        {
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(278)) 
        };

        _sut = new SolarPanelInstallation(solarPanels);
    }

    [Because]
    public void Of()
    {
        _theoreticalCapacity = _sut.CalculateTheoreticalCapacity();
    }    

    [Observation]
    public void Then_it_should_yield_the_total_capacity_of_all_solar_panels_of_the_installation()
    {
        var expectedCapacity = _sut.SolarPanels
            .Select(solarPanel => solarPanel.Capacity.Value)
            .Sum();

        _theoreticalCapacity.Should_be_equal_to(Watts.Of(expectedCapacity));
    }

    private SolarPanelInstallation _sut;
    private Watts _theoreticalCapacity;
}



expectedCapacity 변수의 값이 어떻게 계산되는지 확인하십시오. 이는 SolarPanelInstallation 클래스의 CalculateTheoreticalCapacity 메서드에서 계산하는 것과 매우 유사합니다. 각각의 구현이 용량을 계산하는 방식은 약간 다르지만 이 예에서 테스트 코드에는 프로덕션 코드와 동일한 지식이 포함되어 있다는 결론을 내릴 수 있습니다. 때로는 개발자가 프로덕션 코드에서 직접 "빌려온"테스트를 만나기도 합니다.

이것은 state verification tests이 프로덕션 코드에 너무 밀접하게 결합된 좋은 예이기도 합니다. 따라서 CalculateTheoreticalCapacity 메서드의 구현이 리팩터링될 때 테스트 코드도 수정해야 할 가능성이 상당히 높습니다.

또한 이 테스트를 읽고 예상 값이 무엇인지 파악하는 것이 더 어렵습니다. 이와 같은 쉬운 예의 경우 그렇게 많은 추가 두뇌 주기가 필요하지 않습니다. 그러나 더 복잡한 알고리즘이나 비즈니스 로직을 사용하면 개발자는 예상 값이 얼마인지 파악하기 위해 디버그 모드에서 테스트를 실행하는 경우가 많습니다. 가독성은 어떻습니까?

이 테스트의 개선된 버전을 살펴보겠습니다.

[Specification]
public class When_calculating_the_theoretical_capacity_of_a_solar_panels_installation
{
    [Establish]
    public void Context()
    {
        var solarPanels = new[]
        {
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(278)) 
        };

        _sut = new SolarPanelInstallation(solarPanels);
    }

    [Because]
    public void Of()
    {
        _theoreticalCapacity = _sut.CalculateTheoreticalCapacity();
    }    

    [Observation]
    public void Then_it_should_yield_the_total_capacity_of_all_solar_panels_of_the_installation()
    {
        _theoreticalCapacity.Should_be_equal_to(Watts.Of(1014));
    }

    private SolarPanelInstallation _sut;
    private Watts _theoreticalCapacity;
}



여기에서는 계산 결과로 예상되는 값을 제공했습니다. 그게 다야! 더 이상 중복된 도메인 지식, 테스트의 긴밀한 결합 및 디버깅이 필요 없습니다. 예상 값은 바로 거기에 있습니다. 단순함은 아름다운 것일 수 있습니다.

테스트에 몰래 들어가는 도메인 지식은 피해야 합니다. 이에 대해 매우 주의하십시오. 테스트 결과를 결정하기 위해 테스트 대상 자체를 사용하지 마십시오.

좋은 웹페이지 즐겨찾기