성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>xUnit 단위 테스트에 메서드/클래스 수준의 문맥 제공 - Fixture</h1> <p> 지난 글을 봤다면, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MSTestv2 단위 테스트에 메서드/클래스/어셈블리 수준의 문맥 제공 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12728'>https://www.sysnet.pe.kr/2/0/12728</a> </pre> <br /> 이제 다른 단위 테스트에서도 문맥이 필요하다는 것을 알 수 있습니다. 단지 제공 방법이 다소 제각각인데요, 일례로 xUnit의 경우에는 Fixture로 문맥 제공을 합니다. 단어가 좀 낯설긴 한데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unit Test에 나오는 Fixture와 Mock은 무엇일까? ; <a target='tab' href='https://zorba91.tistory.com/304'>https://zorba91.tistory.com/304</a> </pre> <br /> 위의 글에서는 "테스트 실행을 위해 베이스라인으로서 사용되는 객체들의 고정된 상태"라고 어찌 보면 원론적인 정의를 하지만 간단하게 "문맥"을 제공하는 걸로 보면 됩니다.<br /> <br /> xUnit에서의 문맥 제공 방법은 다음의 글에서 잘 소개하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Shared Context between Tests ; <a target='tab' href='https://xunit.net/docs/shared-context#class-fixture'>https://xunit.net/docs/shared-context#class-fixture</a> </pre> <br /> 천천히 위의 글을 정리해 볼까요? ^^<br /> <br /> 우선, 메서드 단위의 문맥을 제공하는 방법이 있을 텐데요, MSTest의 경우 이를 위해 TestInitialize/TestCleanup 특성을 적용한 메서드를 만들어 구현을 분리하는 방법도 함께 제공했지만, xUnit의 경우에는 그냥 해당 클래스의 인스턴스를 단위 테스트 메서드마다 생성해서 실행하는 방식만 제공합니다.<br /> <br /> 일례로 다음의 코드를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; namespace ClassLibrary1 { public class Class1 : IDisposable { public int Add(int x, int y) { return x + y; } public int Subtract(int x, int y) { return x - y; } public void Dispose() { System.Diagnostics.Trace.WriteLine("Disposed"); } } } </pre> <br /> 테스트하는 xUnit은 이렇게 구성할 텐데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ClassLibrary1.Tests { public class Class1Tests { [Fact()] public void AddTest() { Class1 cl = new Class1(); Assert.Equal(4, cl.Add(1, 3)); } [Fact()] public void SubtractTest() { Class1 cl = new Class1(); Assert.Equal(-2, cl.Subtract(1, 3)); } } } </pre> <br /> 여기서 반복이 되는 "Class1 cl = new Class1();" 코드를 단순히 단위 테스트 클래스의 생성자와 IDisposable을 이용해 다음과 같이 대체하기만 하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ClassLibrary1.Tests { public class Class1Tests : <span style='color: blue; font-weight: bold'>IDisposable</span> { <span style='color: blue; font-weight: bold'>Class1 _cl = new Class1();</span> <span style='color: blue; font-weight: bold'>public void Dispose() { _cl.Dispose(); }</span> [Fact()] public void AddTest() { Assert.Equal(4, _cl.Add(1, 3)); } [Fact()] public void SubtractTest() { Assert.Equal(-2, _cl.Subtract(1, 3)); } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 반면, 클래스 단위의 초기화는 xUnit도 더 이상 방법이 없습니다. 별도로 부가적인 해법을 내놓아야 하는데, 이때 사용하는 방법이 바로 IClassFixture입니다.<br /> <br /> 이를 위해 문맥으로 유지될 클래스를 하나 별도로 정의하고, 그 내부에 문맥 상태 정보를 담을 인스턴스 필드를 추가합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class DatabaseFixture : IDisposable { SqlConnection _db; public DatabaseFixture() { _db = new SqlConnection(); } public void Run(string cmd) { /* code */ } public void Dispose() { _db.Dispose(); } } </pre> <br /> 그다음 단위 테스트 코드 측에서는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Class1Tests : IDisposable<span style='color: blue; font-weight: bold'>, IClassFixture<DatabaseFixture></span> { Class1 _cl = new Class1(); <span style='color: blue; font-weight: bold'>DatabaseFixture _dbFixture;</span> // 외부에서 단 한 번만 생성해 테스트 클래스의 인스턴스마다 전달 public Class1Tests(<span style='color: blue; font-weight: bold'>DatabaseFixture fixture</span>) { <span style='color: blue; font-weight: bold'>_dbFixture = fixture;</span> } public void Dispose() { _cl.Dispose(); } [Fact()] public void AddTest() { Assert.Equal(4, _cl.Add(1, 3)); _dbFixture.Run("..."); } [Fact()] public void SubtractTest() { Assert.Equal(-2, _cl.Subtract(1, 3)); _dbFixture.Run("..."); } } </pre> <br /> 보는 바와 같이, 해당 개체를 형식 매개 변수로 전달받는 IClassFixture를 상속받고 생성자를 통해 Injection을 받는 방식으로 그 인스턴스를 단일하게 유지하며 사용할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 특이하게, xUnit은 여러 개의 클래스에서도 단 하나의 문맥을 공유할 수 있는 방법을 제공하는데 이때 사용하는 인터페이스가 바로 ICollectionFixture입니다.<br /> <br /> 방법은, 테스트 런타임 시에 문맥 상태 정보를 담고 있는 클래스와 그것의 유일한 인스턴스를 소유할 dummy 클래스를 함께 만들어 두고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 전역적으로 공유될 상태 정보를 갖는 클래스 정의 public class GlobalFixture : IDisposable { static HttpClientHandler _sharedHandler = new HttpClientHandler(); public async Task HttpCall(string url) { using (HttpMessageInvoker httpClient = new HttpMessageInvoker(_sharedHandler, false)) { try { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url); HttpResponseMessage resp = await httpClient.SendAsync(req, CancellationToken.None); string result = await resp.Content.ReadAsStringAsync(); } catch { } } } public void Dispose() { _sharedHandler.Dispose(); } } // 더미 클래스 생성 <span style='color: blue; font-weight: bold'>[CollectionDefinition("Global.Status")]</span> // CollectionDefinition 특성에는 식별자 전달 public class GlobalStateCollection : <span style='color: blue; font-weight: bold'>ICollectionFixture<GlobalFixture></span> { // This class has no code, and is never created. Its purpose is simply // to be the place to apply [CollectionDefinition] and all the // ICollectionFixture<> interfaces. } </pre> <br /> 해당 개체를 공유할 테스트 클래스 측에서 저 ICollectionFixture를 사용하겠다는 명시를 Collection 특성으로 정의하고 생성자를 통해 전달받으면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[Collection("Global.Status")]</span> public class Class1Tests : IDisposable, IClassFixture<DatabaseFixture> { DatabaseFixture _dbFixture; GlobalFixture _globalFixture; public Class1Tests(DatabaseFixture fixture, <span style='color: blue; font-weight: bold'>GlobalFixture globalFixture</span>) { _dbFixture = fixture; <span style='color: blue; font-weight: bold'>_globalFixture = globalFixture;</span> } // ...[생략]... } </pre> <br /> 위의 경우와 같이 다른 테스트 클래스에서도 동일한 GlobalFixture 인스턴스를 전달받겠다고 다음과 같이 추가할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[Collection("Global.Status")]</span> public class AnotherTests : IDisposable { GlobalFixture _globalFixture; public Class1Tests(<span style='color: blue; font-weight: bold'>GlobalFixture globalFixture</span>) { <span style='color: blue; font-weight: bold'>_globalFixture = globalFixture;</span> } // ...[생략]... } </pre> <br /> 어찌 보면, MSTest에서는 저런 문맥 정보를 AssemblyInitialize/AssemblyCleanup에서 했다고 봐도 무방할 것입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1226
(왼쪽의 숫자를 입력해야 합니다.)