Autofixture: AutoFixture.AutoNSubstitute๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž˜๋ชป๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” NSubstitute.ReceivedCalls()

์— ๋งŒ๋“  2018๋…„ 03์›” 28์ผ  ยท  10์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: AutoFixture/AutoFixture

'Class.ReceivedCalls()' ๋ฐ 'var tmp = Class.Received(int).Property;'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งŽ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ†ตํ™” ์ˆ˜๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. AutoFixture์˜ v3์—์„œ๋Š” ํŠนํžˆ ๋” ์ด์ƒ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š” ์†์„ฑ์˜ ํ˜ธ์ถœ ์ˆ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ณด๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ํšŸ์ˆ˜๋Š” ์—ฌ์ „ํžˆ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ:

    public interface IRunSpeed
    {
        int Speed { get; }
    }

    public class GetToDaChoppa
    {
        private readonly IRunSpeed _runSpeed;

        public GetToDaChoppa(IRunSpeed runSpeed)
        {
            _runSpeed = runSpeed ?? throw new ArgumentNullException(nameof(runSpeed));
        }

        public void DoItNow()
        {
            var runningSpeed = _runSpeed.Speed;
        }
    }

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ:

        [Fact]
        public void DoItNow_WithOutAutoNSubstitute()
        {
            // Arrange
            var runSpeed = Substitute.For<IRunSpeed>();
            runSpeed.Speed.Returns(2);

            var sut = new GetToDaChoppa(runSpeed);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Single(runSpeed.ReceivedCalls());
        }

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Single(runSpeed.ReceivedCalls());

๋‚ด AutoNSubstituteDataAttribute๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

        public class AutoNSubstituteDataAttribute : AutoDataAttribute
        {
            public AutoNSubstituteDataAttribute()
                : base(() => new Fixture()
                           .Customize(new AutoNSubstituteCustomization()))
            {
            }
        }

์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ 'DoItNow_WithOutAutoNSubstitute'๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‘ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ 'DoItNow_UsingAutoNSubstitute'๋Š” 'ios.Received(1).Speed;'์— ๋Œ€ํ•ด 2๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
๋˜ํ•œ 'runSpeed.ReceivedCalls();'์— ๋Œ€ํ•ด 2๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋•Œ๋ฌธ์— ํ˜„์žฌ ์†”๋ฃจ์…˜๋‹น ํ…Œ์ŠคํŠธ ์‹คํŒจ๊ฐ€ 1000๊ฐœ ์ด์ƒ์ด๋ฏ€๋กœ ํ˜„์žฌ ์†”๋ฃจ์…˜์„ v4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋˜๋Š” ์ˆ˜์ • ์‚ฌํ•ญ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ์œ„์น˜์— ๋Œ€ํ•œ ์ง€์นจ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

question

๋ชจ๋“  10 ๋Œ“๊ธ€

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์‹ค, ์ด ๋ฌธ์ œ๋Š” AutoFixture์™€ ์•„๋ฌด ๊ด€๋ จ์ด ์—†์ง€๋งŒ ์šฐ๋ฆฌ๋„ ์˜ํ–ฅ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค ๐Ÿ˜•

์ด ๋ฌธ์ œ๋Š” ๋Œ€๋ถ€๋ถ„ xUnit์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋ฉฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์ž‘์„ฑํ•˜๋ฉด ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.
```c#
[์ด๋ก , AutoNSubstituteData]
๊ณต๊ฐœ ๋ฌดํšจ DoItNow_UsingAutoNSubstitute_CreateManually(IFixture ๊ณ ์ • ์žฅ์น˜)
{
// ๋งˆ๋ จํ•˜๋‹ค
var runSpeed โ€‹โ€‹= fixture.Freeze();
var sut = ๊ณ ์ •๋ฌผ.๋งŒ๋“ค๊ธฐ();
runSpeed.Speed.Returns(49);

//Act
sut.DoItNow();

//Assert
var tmp = runSpeed.Received(1).Speed;
Assert.Single(runSpeed.ReceivedCalls());

}
```

ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ xUnit์€ ํ…Œ์ŠคํŠธ ์ด๋ฆ„์˜ ํ˜•์‹์„ ์ง€์ •ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์œ ํ˜•์ด ์•Œ๋ ค์ ธ ์žˆ์ง€ ์•Š๊ณ  ToString() ์˜ค๋ฒ„๋กœ๋“œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๊ตฌ์กฐ์  ๊ฒ€์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์†์„ฑ ๊ฐ’์„ ์žฌ๊ท€์ ์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ด๋ฆ„์„ ํ™•์ธํ•˜๋ฉด ๋‹ค์Œ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
image

์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด xUnit์€ ํ…Œ์ŠคํŠธ ์ธ์ˆ˜๋ฅผ ๋ฉ‹์ง€๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด Speed ์†์„ฑ ๊ฐ’์„ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ผ๋ฐ˜์ ์ธ ํ˜ธ์ถœ์ด๋ฏ€๋กœ NSubstitute๋Š” ์ด๋ฅผ ํ˜ธ์ถœ๋กœ ๊ฐ„์ฃผํ–ˆ์Šต๋‹ˆ๋‹ค.

v3์—์„œ๋Š” ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ด์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ ์—์„œ ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ์€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐฉ๊ธˆ xUnit2 + AutoFixture v3 ํ…Œ์ŠคํŠธํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋งˆ๋„ xUnit๋„ ์—…๊ทธ๋ ˆ์ด๋“œํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์— ๊ด€ํ•ด์„œ๋Š” ์ข‹์€ ์†Œ์‹๊ณผ ๋‚˜์œ ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ข‹์€ ์†Œ์‹์€ ํ”„๋ก์‹œ ID๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ToString() ๋ฉ”์„œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— NSubsitute์˜ ๋‹ค์Œ ์ฃผ์š” ๋ฒ„์ „์—์„œ ํ•ด๊ฒฐ๋  ๊ฒƒ์ด๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ xUnit์€ ๋” ์ด์ƒ ์†์„ฑ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
image

๋‚˜์œ ์†Œ์‹์€ NSubsitute v4๊ฐ€ ์•„์ง ์ถœ์‹œ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ œ์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • NSubsitute v4๊ฐ€ ์ถœ์‹œ๋  ๋•Œ๊นŒ์ง€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์—ฐ๊ธฐํ•ฉ๋‹ˆ๋‹ค.
  • NSubsitute์˜ ์ตœ์‹  master ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋กœ์ปฌ ๋นŒ๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  NuGet ํŒจํ‚ค์ง€ ๋Œ€์‹  ์ž์ฒด NSubsitute.dll ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. v4๊ฐ€ ๋ฆด๋ฆฌ์Šค๋œ ํ›„ NuGet ํŒจํ‚ค์ง€๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • xUnit ๋ฒ„์ „๋„ ์—…๋ฐ์ดํŠธํ•œ ๊ฒฝ์šฐ(์ด์ „์— ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์€ ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Œ) ํ•ด๋‹น ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋˜๋Œ๋ฆฌ๊ณ  xUnit์˜ ์ด์ „ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

๋ถˆํŽธ์„ ๋“œ๋ ค ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•ˆํƒ€๊น๊ฒŒ๋„ AutoFixture ์ธก์—์„œ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ฌด ๊ฒƒ๋„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋ฐฉ๊ธˆ ์ด๊ฒƒ์„ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์นจ๋‚ด ReceivedCalls๋ฅผ ์ฃผ์žฅํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ๋‹ค์‹œ ์„ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.
์—ฌ์ „ํžˆ ์‹คํŒจํ•˜๋Š” ๊ฒƒ์€ ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ Received(1).Speed โ€‹โ€‹Daniel๊ณผ ๊ฐ™์€ Received(x)์— ๋Œ€ํ•œ ์ฃผ์žฅ์ž…๋‹ˆ๋‹ค.

snip_20180328181559

๋ชจ์ž์— ๋Œ€ํ•œ ๋‹ต๋ณ€ / ์†”๋ฃจ์…˜๋„ ์žˆ์Šต๋‹ˆ๊นŒ?

๊ฐ™์€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ ์ค‘์ด์‹ ๊ฐ€์š”? :) ๊ทธ๋ ‡๋‹ค๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ ๋ฐฉ๋ฒ•์„ ๋ช…ํ™•ํžˆ ํ•ด์ฃผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์œ„์—์„œ ์ œ์•ˆํ•œ ์˜ต์…˜์€ ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ์— ๋ชจ๋‘ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ - ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ช…ํ™•ํžˆ ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐ™์€ ํšŒ์‚ฌ์—์„œ.
์šฐ๋ฆฌ๋Š” xunit 1.9 + nsubstitute 2 / 3์„ ์˜ค๋žซ๋™์•ˆ ์‚ฌ์šฉํ•ด์™”๊ณ  ๋งˆ์นจ๋‚ด ๋‚ด๋ถ€ ์–ด์…ˆ๋ธ”๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค(nuget ํŒจํ‚ค์ง€์ด๋ฏ€๋กœ ํ•˜๋‚˜์˜ ์ฐธ์กฐ๋งŒ ์žˆ๋Š” ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์†Œ๋ฆฌ๋งŒํผ ์‰ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค ๋ ) xunit2์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ด ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ๋ช‡ ๊ฐ€์ง€๋ฅผ ์‹œ๋„ํ–ˆ๊ณ  ๋งˆ์นจ๋‚ด ๊ทธ๊ฒƒ์ด ์ž๋™ ๊ณ ์ •๊ณผ ๊ด€๋ จ์ด ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด frozen-์†์„ฑ๊ณผ ํ•˜๋“œ ์ฝ”๋”ฉ๋œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํšŸ์ˆ˜ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๋‹ค๋‹ˆ์—˜์ด ์ด ์ผ€์ด์Šค๋ฅผ ์—ฐ ํ›„ ๊ทธ๋Š” ๋‚˜์—๊ฒŒ ๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ๋งํฌ๋ฅผ ๊ฒŒ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.

์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ nuget 3.1 ์ฐธ์กฐ๋ฅผ ์ƒˆ๋กœ ์ปดํŒŒ์ผ๋œ nsubstitute ํ”„๋กœ์ ํŠธ์˜ ์ฐธ์กฐ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ํ›„ Receiced(x)๊ฐ€ ์—ฌ์ „ํžˆ ์ž˜๋ชป๋œ ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ํ‰๊ฐ€ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— 178๊ฐœ์˜ ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ ๋Œ€์‹  118๊ฐœ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ํ”„๋กœ์ ํŠธ๋Š” ํ˜„์žฌ xunit2, nsubstitute์˜ ํ˜„์žฌ repo ๋ฒ„์ „ ๋ฐ autofixture 4.2๊ฐ€ ์ฐธ์กฐ๋œ .net 4.5.2๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ผํ•œ ๋‘ ๊ฐ€์ง€ ์ด์œ ๋กœ ์‹คํŒจํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ์—ฌ์ „ํžˆ ์‹คํŒจํ•œ ์ผ๋ถ€ ์ˆ˜์‹  ํ˜ธ์ถœ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
๋‚˜๋Š” ๋‹ค์‹œ ์‚ฌ๋ฌด์‹ค๋กœ ๋Œ์•„๊ฐˆ ๋•Œ(๋ถ€ํ™œ์ ˆ์„ ์œ„ํ•ด) ๋” ๊นŠ์ด ๋“ค์—ฌ๋‹ค๋ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
ํ•œํŽธ @dklinger ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dklinger @evilbaschdi ํ™•์ธ ํ›„ ํ›„์† ์กฐ์น˜๋ฅผ ์ทจํ•ด ์ฃผ์‹ญ์‹œ์˜ค. ํŒจ์น˜๋ฅผ ์ ์šฉํ•œ ํ›„์—๋„ ๋ฌธ์ œ๊ฐ€ ๊ณ„์† ๋‚˜ํƒ€๋‚˜๋Š” ์ด์œ ๋Š” ๋งค์šฐ ํฅ๋ฏธ๋กญ์Šต๋‹ˆ๋‹ค.

์•„์ง ์กฐ์‚ฌ๊ฐ€ ์ง„ํ–‰ ์ค‘์ž„์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ๋ฌธ์ œ๋ฅผ ๋‹ค์‹œ ์—ฝ๋‹ˆ๋‹ค.

์ข‹์•„์š”, ๊ทธ๋ž˜์„œ ์ง€๋‚œ ๋ช‡ ์‹œ๊ฐ„ ๋™์•ˆ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ: ๊ท€ํ•˜์˜ ๋‹ต๋ณ€, ์„ค๋ช… ๋ฐ ์ œ์•ˆ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋งŽ์€ ๋„์›€์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
NSubstitute์˜ v4๋ฅผ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€๋งŒ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฌธ์ œ๋ฅผ ์™„์ „ํžˆ ํ•ด๊ฒฐํ•˜์ง€๋Š” ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ์†์„ฑ์ด ์•„๋‹Œ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ SUT ํ˜ธ์ถœ์— ๋Œ€ํ•ด์„œ๋งŒ ์ž˜๋ชป๋œ ReceivedCalls ์ˆ˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ˜„์žฌ ์ž‘๋™ ์ค‘์ธ ์ฝ”๋“œ์˜ ์˜ˆ:

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

ํ•˜์ง€๋งŒ "GetToDaChoppa.DoItNow();" ๋ฉ”์„œ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด "GetToDaChoppa.DoItNow" ์†์„ฑ์ด ๋˜๋ ค๋ฉด ReceivedCallsCount๊ฐ€ ๋‹ค์‹œ +1์ž…๋‹ˆ๋‹ค.

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            var x = sut.DoItNow;

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

์ž‘์—… ๋ฐฉ๋ฒ• ๊ตฌํ˜„์„ ์œ„ํ•ด ์ด๊ฒƒ์„ ์–ป์—ˆ์„ ๋•Œ xUnit ์ด๋ฆ„ ์ง€์ •๊ณผ ๋‹ค์‹œ ๊ด€๋ จ์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
image

๊ทธ๋ฆฌ๊ณ  ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ์†์„ฑ ๊ตฌํ˜„์„ ์œ„ํ•œ ๊ฒƒ:
image

xUnit์ด ํ…Œ์ŠคํŠธ ์ด๋ฆ„์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ’์„ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•ด SUT ์†์„ฑ์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ SUT ๋ฉ”์„œ๋“œ์—๋Š” ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚ด ์ฒซ ๋ฒˆ์งธ ์ƒ๊ฐ์€ ๋ฌดํšจ์ธ "GetToDaChoppa()" ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜ ๊ฐ’๊ณผ ๊ด€๋ จ์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ "public int GetToDaChoppa()"๋กœ ๋ณ€๊ฒฝํ•œ ํ›„์—๋„ xUnit์€ ์—ฌ์ „ํžˆ โ€‹โ€‹ํ…Œ์ŠคํŠธ ์ด๋ฆ„์— ๋Œ€ํ•œ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์–ป๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ์ œ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์ง€๊ธˆ์€ ๋‹ค์‹œ ๋ถ™์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์ด AutoFixture์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ๋ฐ ์ „์ ์œผ๋กœ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šฐ๋ฆฌ๋ณด๋‹ค ๋ชจ๋“  ํŒจํ‚ท์„ ๋” ์ž˜ ์•Œ๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ œ์•ˆ์„ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

  • NSubstitute์— ๋Œ€ํ•œ ๋ฌธ์ œ๋ฅผ ์—ฌ๋Š” ๊ฒƒ?
  • xUnit2์— ๋Œ€ํ•œ ๋ฌธ์ œ๋ฅผ ์—ฌ๋Š” ์ค‘์ด์‹ ๊ฐ€์š”?
  • ์•„๋‹ˆ๋ฉด ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๊ฒƒ?

@dklinger ํ›„์† MCVE ๋ฅผ ๊ณต์œ ํ•ด ์ฃผ

๊ทธ ํ›„์— ์™œ ๊ทธ๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•˜๊ณ  ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ด์•ผ ํ•˜๋Š”์ง€ ์กฐ์‚ฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์‚ฌ ํ•ด์š”.

๋„ค, ๋ฌผ๋ก ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์žˆ์–ด:

ํ…Œ์ŠคํŠธ ์ค‘์ธ ์‹œ์Šคํ…œ:

    public interface IRunSpeed
    {
        int Speed { get; }
        void Dude();
        int Dude2();
    }

    public class GetToDaChoppa
    {
        private readonly IRunSpeed _runSpeed;

        public GetToDaChoppa(IRunSpeed runSpeed)
        {
            _runSpeed = runSpeed ?? throw new ArgumentNullException(nameof(runSpeed));
        }

        public int DoItNow
        {
            get
            {
                var runningSpeed = _runSpeed.Speed;
                return 0;
            }
        }
    }

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค:

        [Theory, AutoNSubstituteData]
        public void DoItNowAsProperty_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            var x = sut.DoItNow;

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

ํ˜„์žฌ ์ €๋Š” https://github.com/xunit/xunit/issues/1386 ๋ฐ https://github.com/AutoFixture/AutoFixture/issues/805 ์—์„œ ํ† ๋ก ์„ ๋”ฐ๋ผ์žก์œผ๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๊ณ 
๋˜ํ•œ DisplayName ์†์„ฑ์„ ์žฌ์ •์˜ํ•˜๋Š” ๋‚˜๋งŒ์˜ ์ด๋ก  ์†์„ฑ์„ ๋งŒ๋“ค๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ xUnit์ด ์—ฌ์ „ํžˆ ๋‚ด๋ถ€์ ์œผ๋กœ ์ž๋™ ์ƒ์„ฑ๋œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ์ฐธ๊ณ ์šฉ์ผ ๋ฟ์ด๋ฉฐ ์œ„์˜ ๋ฐ๋ชจ ์ฝ”๋“œ์™€ ์ „ํ˜€ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค.

        public sealed class MyTheoryAttribute : TheoryAttribute
        {
            public MyTheoryAttribute([CallerMemberName] string memberName = null)
            {
                DisplayName = "MyTestCase";
            }
        }

image

์•ˆ๋…•, ๋˜ ๋‚˜์•ผ :)
๋ณตํ•ฉ ์œ ํ˜•์ธ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ• ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๋ชจ๋“  ์†์„ฑ ๋ฐ ํ•„๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ์‹ค์ œ๋กœ xUnit2์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ ์ค„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. https://github.com/xunit/assert.xunit/blob/2b70a9b0c5bb291f98472ec24cec437acf8d65c8/Sdk/ArgumentFormatter.cs#L156

์ง€์›ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ํ† ๋ก ์ด ๊ณ„์†๋˜์–ด์•ผ ํ•˜๋Š” xUnit-Repo์—์„œ ๋ฌธ์ œ๋ฅผ ๋‹ค์‹œ ์—ด์—ˆ์Šต๋‹ˆ๋‹ค. https://github.com/xunit/xunit/issues/1682

@dklinger ์ž์„ธํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์‹ค์ œ๋กœ ์ƒ๋‹นํžˆ ๊นŒ๋‹ค๋กญ๊ณ  ์–ด๋–ป๊ฒŒ๋“  ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. AutoFixture์™€ NSubsitute ๊ด€์ ์—์„œ ๋ณด๋ฉด ์ฝ”๋“œ๊ฐ€ xUnit ๋‚ด๋ถ€ ๊นŠ์ˆ™ํ•œ ๊ณณ์—์„œ ํ˜ธ์ถœ๋˜๋“  ํ…Œ์ŠคํŠธ ๋ณธ๋ฌธ์—์„œ ํ˜ธ์ถœ๋˜๋“  ์ฐจ์ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ NSubstitute์˜ ๋ช…ํ™•ํ•œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

runSpeed.ClearReceivedCalls();

์ด ์ฝ”๋“œ๋Š” ์ •ํ™•ํ•œ ํ˜ธ์ถœ ์ˆ˜๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฐ ํ…Œ์ŠคํŠธ์˜ ํ”„๋กค๋กœ๊ทธ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ช‡ ๊ฐ€์ง€ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ ๋ถ„๋ช…ํžˆ ์ˆ˜์ฒœ ๊ฐœ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›์œผ๋ฉด๋ณ„๋กœ ๋„์›€์ด๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค ๐Ÿ˜…

NSubsitute + xUnit2 + AutoFixture ํ†ตํ•ฉ์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๊ณ  ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๋ฌธ์ œ๋กœ ๊ณ ํ†ต๋ฐ›๋Š” ๊ฒƒ์€ ์œ ๊ฐ์ž…๋‹ˆ๋‹ค. AutoFixture ์ œํ’ˆ์€ ์•…๋ชฝ์„ ๊พธ๊ธฐ๋ณด๋‹ค๋Š” ์‚ถ์„ ๋‹จ์ˆœํ™”ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ˜• xUnit์˜ ์ง์›๋“ค์ด ์ „์ฒด ํ”„๋กœ์ ํŠธ์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋น ๋ฅธ ๋ฐฉ๋ฒ•์„ ์กฐ์–ธํ•ด์ฃผ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ƒํ™ฉ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ ์ธก์—์„œ ๋ญ”๊ฐ€๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋ฉด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰