๋ค์์ ์์ฑ ๊น์ด ์ ํ๊ธฐ ๋์ ์ ์์ ๋๋ค.
Fixture.Behaviors.Add(์๋ก์ด GenerationDepthBehavior(2));
๋ด๋ถ์ ์ด๊ธฐ ๋๋ฌธ์ ํ ์คํธ๋ฅผ ์ํด ComposeIfMultiple์ ๋ค์ ๊ตฌํํด์ผ ํ์ต๋๋ค.
```c#
๊ณต๊ฐ ํด๋์ค GenerationDepthBehavior : ISpecimenBuilderTransformation
{
๊ฐ์ธ const int DefaultGenerationDepth = 1;
๊ฐ์ธ ์ฝ๊ธฐ ์ ์ฉ int generationDepth;
public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}
public GenerationDepthBehavior(int generationDepth)
{
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.generationDepth = generationDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}
}
๊ณต๊ฐ ์ธํฐํ์ด์ค IGenerationDepthHandler
{
object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}
๊ณต๊ฐ ํด๋์ค DepthSeededRequest : SeededRequest
{
๊ณต๊ฐ ์ ์ ๊น์ด { get; }
public DepthSeededRequest(๊ฐ์ฒด ์์ฒญ, ๊ฐ์ฒด ์๋, int ๊น์ด) : base(์์ฒญ, ์๋)
{
๊น์ด = ๊น์ด;
}
}
๊ณต๊ฐ ํด๋์ค GenerationDepthGuard : ISpecimenBuilderNode
{
๊ฐ์ธ ์ฝ๊ธฐ ์ ์ฉ ThreadLocal
= ์๋ก์ด ThreadLocal
private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public GenerationDepthGuard(ISpecimenBuilder builder)
: this(builder, EqualityComparer<object>.Default)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
int generationDepth)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
generationDepth)
{
}
public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
this.GenerationDepth = 1;
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer)
: this(
builder,
depthHandler,
comparer,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer,
int generationDepth)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.Builder = builder;
this.GenerationDepthHandler = depthHandler;
this.Comparer = comparer;
this.GenerationDepth = generationDepth;
}
public ISpecimenBuilder Builder { get; }
public IGenerationDepthHandler GenerationDepthHandler { get; }
public int GenerationDepth { get; }
public int CurrentDepth { get; }
public IEqualityComparer Comparer { get; }
protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();
public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
request,
this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}
public object Create(object request, ISpecimenContext context)
{
if (request is SeededRequest)
{
int currentDepth = -1;
var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count > 0)
{
currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
}
DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);
if (depthRequest.Depth >= this.GenerationDepth)
{
return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
}
requestsForCurrentThread.Push(depthRequest);
try
{
return this.Builder.Create(request, context);
}
finally
{
requestsForCurrentThread.Pop();
}
}
else
{
return this.Builder.Create(request, context);
}
}
public virtual ISpecimenBuilderNode Compose(
IEnumerable<ISpecimenBuilder> builders)
{
var composedBuilder = ComposeIfMultiple(
builders);
return new GenerationDepthGuard(
composedBuilder,
this.GenerationDepthHandler,
this.Comparer,
this.GenerationDepth);
}
internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
ISpecimenBuilder singleItem = null;
List<ISpecimenBuilder> multipleItems = null;
bool hasItems = false;
using (var enumerator = builders.GetEnumerator())
{
if (enumerator.MoveNext())
{
singleItem = enumerator.Current;
hasItems = true;
while (enumerator.MoveNext())
{
if (multipleItems == null)
{
multipleItems = new List<ISpecimenBuilder> { singleItem };
}
multipleItems.Add(enumerator.Current);
}
}
}
if (!hasItems)
{
return new CompositeSpecimenBuilder();
}
if (multipleItems == null)
{
return singleItem;
}
return new CompositeSpecimenBuilder(multipleItems);
}
public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
๊ณต๊ฐ ํด๋์ค GenerationDepthHandler : IGenerationDepthHandler
{
๊ณต๊ฐ ๊ฐ์ฒด HandleGenerationDepthLimitRequest(
๊ฐ์ฒด ์์ฒญ,
IEnumerable
์๋ ํ์ธ์, ์์ด๋์ด๋ฅผ ๊ณต์ ํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค! :+1:
์ด ๊ธฐ๋ฅ์ ์ด์ ๋ฅผ ์ค๋ช ํด ์ฃผ์๊ฒ ์ต๋๊น? ์ ์ฉํ๋ค๊ณ ์๊ฐํ ํน์ ์๋๋ฆฌ์ค๊ฐ ์์ต๋๊น?
์๋
ํ์ธ์, ์ ๋ ๊น์ด๊ฐ ๋ฌด๊ธฐํ ์ ํ๋์ง๋ง ์ฌ๊ท์ ์ด์ง ์์ ORM ์ํฐํฐ ๋๋ ์ธ๋ถ API ํด๋์ค์ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค.
์ ๋ ๋จ์ ํ
์คํธ, ํตํฉ ํ
์คํธ ๋ฐ ๊ธฐ๋ฅ ํ
์คํธ์ AutoFixture๋ฅผ ์ฌ์ฉํฉ๋๋ค. ํต์ฌ ์ํฐํ๋ผ์ด์ฆ ํ๋ ์์ํฌ ๊ธฐ๋ฅ์ ๋ํ ๋จ์ ํ
์คํธ. ๋ฆฌํฌ์งํ ๋ฆฌ ํจํด ๋ฐ ํ
์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ก์ธ์ค๊ฐ ์๋ ๋ณต์กํ ์์
๋จ์์ ๋ํ ํ
์คํธ์ ํตํฉ ํ
์คํธ. ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ๋ํ ์ก์ธ์ค ๊ถํ์ด ์๋ ๊ธฐ๋ฅ ํ
์คํธ.
AutoMoqDataAttribute์ ์ง์ ๋ฃ์์ต๋๋ค.
```c#
๊ณต๊ฐ ํด๋์ค AutoMoqDataAttribute : AutoDataAttribute
{
///
/// ๊ธฐ๋ณธ ์์ฑ์.
///
/// ๋ฌด์ ํ ์์ฑ ๊น์ด์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ์ 0์
๋๋ค.
๊ณต๊ฐ AutoMoqDataAttribute(int ์์ฑ ๊น์ด = 0)
: ๊ธฐ๋ณธ(() =>
{
IFixture ๊ณ ์ ์ฅ์น = new Fixture().Customize(new AutoMoqCustomization());
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
if (generationDepth > 0)
{
fixture.Behaviors.Add(new GenerationDepthBehavior(generationDepth));
}
return fixture;
})
{
}
}
```
๋ํ ComposeIfMultiple์ ๊ณต๊ฐํ๋๋ก ์ ์ํฉ๋๋ค. ์ฌ์ฉ์ ์ ์ ๋์ ๊ตฌํ์ ๋ ์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
๋๋ ๊ทธ ํ๋์ด ๋ฌด์์ ํ๋์ง ์ดํดํฉ๋๋ค. ์ฃผ๋ ์ง๋ฌธ์ ๋ชฉ์ ๊ณผ ์๋๋ฆฌ์ค์ ๋๋ค.
์๋ ํ์ธ์, ์ ๋ ๊น์ด๊ฐ ๋ฌด๊ธฐํ ์ ํ๋์ง๋ง ์ฌ๊ท์ ์ด์ง ์์ ORM ์ํฐํฐ ๋๋ ์ธ๋ถ API ํด๋์ค์ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค.
๊ทธ๋์ ๋ด๊ฐ ์ดํดํ๋ ํ ๋น์ ์ด ๋ชฉํ๋กํ๋ ์ฃผ์ ๋ชฉํ๋ ์ฑ๋ฅ์ ๋๋ค. ๊ธฐ๋ฅ์ ๊ด์ ์์ ๋ชจ๋ ๊ฒ์ด ์ ์๋ํ๊ณ CPU ํฑ ๋ช ๊ฐ๋ฅผ ์ ์ฝํ๊ณ ์ถ์ ๋ฟ์ ๋๋ค.
์ด ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ ์ง ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๊ฒ์ ์ฝ๊ฐ ์ด๋ ต์ต๋๋ค. ๋๋ ๋น์ ์ด ์ค์ ๋ก ๋งค์ฐ ๊น์ ์ค์ฒฉ์ ๊ฐ์ง ์ ํ ๋ชจ๋ธ์ ๊ฐ์ง ์ ์๊ณ ๊ฑฐ๊ธฐ์์ ์ฑ๋ฅ ํฅ์์ด ์ค์ํ๋ค๋ ๊ฒ์ ์ดํดํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ฌ๊ธฐ์๋ ๋ช ๊ฐ์ง ๋จ์ ๋ ์์ต๋๋ค.
ํ์์ ์ด ๊ธฐ๋ฅ์ด ํ์ํ๊ณ ์ฌ์ฉ์ ๋์ํ๋ ๊ฒ์ ๊ด์ฐฎ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ผ์ ๋ง์ด ๋ณต์กํ๊ฒ ํ๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ธ ์ฉ๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ง์ ์ ์ฌ์ ์ธ ์๋๋ฆฌ์ค๊ฐ ์์ผ๋ฉฐ AutoFixture๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๊ธฐ๋ฅ๋ง ์ ๊ณตํ๋ ค๊ณ ํฉ๋๋ค. ๊ณ ๊ธ ๋นํธ๋ ํ์ฅ์ฑ ๊ธฐ๋ฅ์ผ๋ก ๋ค๋ฃน๋๋ค. ์ด ํน๋ณํ ๊ฒฝ์ฐ์ ๋๋ ๊ทธ ๊ธฐ๋ฅ์ด ๊ทธ๊ฒ์ ์ ํ์ ์ผ๋ถ๋ก ๋ง๋๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด์ง ์๋ค๊ณ ๋งํ๊ณ ์ถ์ต๋๋ค. ๋ ๋ค๋ฅธ ์ ์ ๊ตฌํ์ด ๋งค์ฐ ์๊ธฐ ๋๋ฌธ์(์๋ ์ฐธ์กฐ) ํ ์คํธ ํ๋ก์ ํธ์์ ๋ก์ปฌ๋ก ๊ตฌํํ๋ ๋ฐ ๋ฌธ์ ๊ฐ ์์ด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค.
๋ํ ComposeIfMultiple์ ๊ณต๊ฐํ๋๋ก ์ ์ํฉ๋๋ค. ์ฌ์ฉ์ ์ ์ ๋์ ๊ตฌํ์ ๋ ์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
์ด๋ฏธ #657์ ๋ํด ๋ ผ์ํ์ต๋๋ค.
๊ทธ๊ฑด ๊ทธ๋ ๊ณ , ๊ตฌํ์ด ์ฝ๊ฐ ๋จ์ํ ๋ ์ ์์ต๋๋ค (๋ด๊ฐ ๋๋ฝ ๋ ๊ฒ์ด ์๋ค๋ฉด).
```c#
๊ณต๊ฐ ํด๋์ค GenerationDepthBehavior: ISpecimenBuilderTransformation
{
๊ณต๊ฐ ์ ์ ๊น์ด { get; }
public GenerationDepthBehavior(int depth)
{
Depth = depth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}
private class IsSeededRequestComparer : IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
return x is SeededRequest && y is SeededRequest;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
}
}
}
```
@moodmosaic ์๊ฒฌ๋ ๊ณต์ ํด ์ฃผ์๊ฒ ์ต๋๊น? ๐
https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150์ ํ์๋ ์๋ ์ด ์์ ๋์์ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด ์์ ์์ ๋ด์ฅ ์๊ณ ๋ฆฌ์ฆ์ ๋ณ๊ฒฝํด์๋ ์ ๋๋ค๊ณ ์๊ฐํฉ๋๋ค.
๊ทธ๋ฌ๋ ์ฐ๋ฆฌ ๊ฐ ๊ณ ๋ คํ
๊ทธ๋ฐ ๋ค์ ์ด์ ๊ฐ์ ์ถ๊ฐ ์ต์ ํ๋ฅผ ๊ณ ๋ คํ ์ ์์ต๋๋ค.
@moodmosaic์ด ์ ์์ด ๊ธฐ๋ณธ ๋์์ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์๋๋ผํ๊ณ ์ถ์ง GenerationDepthBehavior
(๋น์ ์ ํ์์ ๋ฐ๋ผ ํ์ฑํ ํ ์ ์๋๋ก)๋ฅผ AutoFixture ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ผ๋ถ๋ฅผ. ์ด์ ๋ํด ์ด๋ป๊ฒ ์๊ฐํ์ญ๋๊น? :๋์ง:
@zvirja ์ค์ ๋ก ์ฑ๋ฅ์์ ์ด์ ๊ฐ ์๋๋๋ค. ๋ฌด๊ธฐํ ์ ํ์ ๋ฌธ์ ๋ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์์ธ๊ฐ ์๋ ํ ์คํธ ์คํจ์ ๋๋ค.
@zvirja , @malylemire1 , ์ด ์์ ๋์์ด ๊ด๋ฒ์ํ ์๋๋ฆฌ์ค๋ฅผ ํด๊ฒฐํ์ง ์๋ ํ ๊ธฐ๋ณธ ์ ๊ณต๋ ํ์๋ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
MCVE๋ฅผ ์ฐพ์ ์ ๋ ์์ง๋ง AFAICT, ๋ฌธ์ ๋ ์ค์ ๋ก ๋จ์ ํ ์คํธ(์: ๊ฒฝ๊ณ/ํตํฉ ํ ์คํธ)๊ฐ ์๋ ์๋ํ๋ ํ ์คํธ์์ ๋ฐ์ํฉ๋๋ค.
AutoFixture๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํตํฉ ํ ์คํธ๋ฅผ ๋ค๋ฃจ๋๋ก ์ค๊ณ๋์ง ์์์ผ๋ฏ๋ก ์ผ๋ถ ์์ ๋์(์ฌ๊ธฐ์ ํ์๋ ๊ฒ๊ณผ ๊ฐ์) ์ด ๋ณด์ฅ๋ฉ๋๋ค. +1: ๊ทธ๋๋ ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ผ๋ถ์ผ ํ์๋ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
@malylemire1 ์ง๊ธ์ ์ด ๊ธฐ๋ฅ์ ๋ํ ์์ฒญ์ด ๋ง์ ๋๋ง ๋ฏธ๋ฃจ๊ณ ๋์ค์ ๊ตฌํํด๋ ๊ด์ฐฎ์๊น์?
์ํ๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ํต์ฌ์ ํฌํจํ ์ ์์ด ์ ๊ฐ์ ๋๋ค ๐ ์ ๊ณต๋ ๊ธฐ๋ฅ๊ณผ ์ฌ๊ธฐ์์ ์ ์ง ๊ด๋ฆฌํ๋ ์ฝ๋ ์ ์ฌ์ด์ ๊ท ํ์ ์ ์งํ๋ ๊ฒ์ด ์ฝ๊ฐ ์ด๋ ต์ต๋๋ค. ์๋ฃจ์ ์์ ์ด ์์ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ฒ์ ํฐ ๋ฌธ์ ๊ฐ ๋์ง ์์ ๊ฒ ๊ฐ์ผ๋ฏ๋ก ์ฐจ๋จ๋์ง ์์ต๋๋ค.
๊ทํ์ ์ ์์ ๋ค์ ํ ๋ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค!
์ ๋ฆฌ์ ์ผ๋ถ๋ก ์ด ๋ฌธ์ ๋ฅผ ์ข ๋ฃํฉ๋๋ค. ์์ด๋์ด๋ฅผ ๊ณต์ ํด ์ฃผ์ ์ ๋ค์ ํ ๋ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค! ๐
@malylemire1 ์ฌ์ฉ ์ฌ๋ก๋ฅผ 100% ์ดํดํฉ๋๋ค.
์ด๊ฒ์ด ๋๊ฒ ํจํค์ง๋ก ์ ๊ณตํ ์ ์๋ ๊ฒ์
๋๊น?
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
@malylemire1 ์ง๊ธ์ ์ด ๊ธฐ๋ฅ์ ๋ํ ์์ฒญ์ด ๋ง์ ๋๋ง ๋ฏธ๋ฃจ๊ณ ๋์ค์ ๊ตฌํํด๋ ๊ด์ฐฎ์๊น์?
์ํ๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ํต์ฌ์ ํฌํจํ ์ ์์ด ์ ๊ฐ์ ๋๋ค ๐ ์ ๊ณต๋ ๊ธฐ๋ฅ๊ณผ ์ฌ๊ธฐ์์ ์ ์ง ๊ด๋ฆฌํ๋ ์ฝ๋ ์ ์ฌ์ด์ ๊ท ํ์ ์ ์งํ๋ ๊ฒ์ด ์ฝ๊ฐ ์ด๋ ต์ต๋๋ค. ์๋ฃจ์ ์์ ์ด ์์ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ฒ์ ํฐ ๋ฌธ์ ๊ฐ ๋์ง ์์ ๊ฒ ๊ฐ์ผ๋ฏ๋ก ์ฐจ๋จ๋์ง ์์ต๋๋ค.
๊ทํ์ ์ ์์ ๋ค์ ํ ๋ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค!