성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>AppDomain에 대한 단위 테스트 시 알아야 할 사항</h1> <p> 단위 테스트에서 AppDomain을 생성 후 AppDomain.DoCallBack 등을 이용해 테스트 실행을 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.VisualStudio.TestTools.UnitTesting; using System; namespace ClassLibrary1.Tests { [TestClass()] public class Class1Tests { [TestMethod()] public void MyMethodTest() { AppDomain domain = AppDomain.CreateDomain("Test"); <span style='color: blue; font-weight: bold'>domain.DoCallBack(() => Console.WriteLine("Hello world"));</span> AppDomain.Unload(domain); } } } </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;' > Test Name: MyMethodTest Test FullName: ClassLibrary1.Tests.Class1Tests.MyMethodTest Test Source: C:\ClassLibrary1Tests\Class1Tests.cs : line 16 Test Outcome: Failed Test Duration: 0:00:00.0091383 Result StackTrace: at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate) at ClassLibrary1.Tests.Class1Tests.MyMethodTest() in C:\ClassLibrary1Tests\Class1Tests.cs:line 18 Result Message: Test method ClassLibrary1.Tests.Class1Tests.MyMethodTest threw exception: System.Runtime.Serialization.SerializationException: Type is not resolved for member 'ClassLibrary1.Tests.Class1Tests+<>c,ClassLibrary1Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. </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;' > Creating new application domains while running a unit?test ; <a target='tab' href='https://malvinly.com/2013/07/30/creating-new-application-domains-while-running-a-unit-test/'>https://malvinly.com/2013/07/30/creating-new-application-domains-while-running-a-unit-test/</a> </pre> <br /> 위의 글을 정리해 보면, DoCallBack으로 전달된 람다 함수가 정의된 어셈블리는 ClassLibrary1Tests.dll인데, 그 어셈블리를 새로운 AppDomain에서는 로드할 수 없기 때문에 예외가 발생하는 것입니다.<br /> <br /> 좀 더 자세히 원인 분석을 해볼까요? ^^<br /> <br /> Visual Studio 2015의 경우, 기본 unit test 도구인 "vstest.executionengine.x86.exe" 실행 파일은 "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow" 폴더에 있습니다. 또한 "vstest.executionengine.x86.exe" 파일 역시 닷넷 프로세스입니다. 여기서 문제의 원인이 시작됩니다.<br /> <br /> 일반적으로 닷넷 프로세스는 해당 EXE 파일이 있는 폴더를 기준으로 하위의 dll들만 접근할 수 있습니다. 이 제약을 벗어나고 싶다면 Assembly.LoadFrom을 하거나, 새롭게 AppDomain을 생성하면서 SetupInformation.ApplicationBase 경로를 대상 DLL이 있는 폴더로 설정해야 합니다. 당연히 마이크로소프트는 단위 테스트를 위해 새로운 AppDomain을 생성하도록 만들었고, 그 테스트가 실행되는 AppDomain의 SetupInformation.ApplicationBase에는 ClassLibrary1Tests.dll 파일이 있는 폴더를 설정해 주었습니다.<br /> <br /> 그런데, 개발자가 직접 생성한 AppDomain의 경우는 어떨까요? <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AppDomain domain = AppDomain.CreateDomain("Test"); domain.DoCallBack(() => Console.WriteLine("Hello world")); AppDomain.Unload(domain); </pre> <br /> SetupInformation.ApplicationBase는 명시적으로 설정하지 않는 한 .exe의 경로를 따라갑니다. 따라서 위의 코드는 SetupInformation.ApplicationBase가 "vstest.executionengine.x86.exe" 실행 파일이 있는 "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow" 폴더로 설정되고 DoCallBack에 전달된 람다 함수가 정의된 ClassLibrary1Tests.dll 파일을 찾을 수 없게 됩니다. 즉, 오류가 발생하는 것이 아주 자연스러운 현상인 것입니다.<br /> <br /> 문제 해결은 간단합니다. 그냥 다음과 같이 SetupInformation.ApplicationBase를 ClassLibrary1Tests.dll 파일이 있는 폴더로 맞춰주면 됩니다. (대개의 경우, 단위 테스트가 실행 중인 AppDomain의 것을 적용하면 됩니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AppDomain domain = AppDomain.CreateDomain("Test", new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence), <span style='color: blue; font-weight: bold'>new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase }</span>); </pre> <br /> <hr style='width: 50%' /><br /> <br /> 예전에 단위 테스트를 위한 Fake에 대해 설명했는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Visual Studio의 단위 테스트 작성 시 Fakes를 이용한 메서드 재정의 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/10858'>http://www.sysnet.pe.kr/2/0/10858</a> </pre> <br /> Shim 문맥이 AppDomain 마다 적용되기 때문에, AppDomain.DoCallBack 등에서 Shim 객체를 사용하는 경우 반드시 다음과 같이 해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [TestClass()] public class Class1Tests { [TestMethod()] public void MyMethodTest() { AppDomain domain = AppDomain.CreateDomain("Test", new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence), new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase }); domain.DoCallBack(() => { <span style='color: blue; font-weight: bold'>using (ShimsContext.Create())</span> { <span style='color: blue; font-weight: bold'>ShimDateTime.NowGet = () => new DateTime(2015, 05, 06); Assert.IsTrue(DateTime.Now.Year == 2015);</span> } } ); AppDomain.Unload(domain); } } </pre> <br /> 그러니까, AppDomain을 벗어난 다음과 같은 식이면 Shim 객체가 제대로 동작하지 않습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [TestMethod()] public void MyMethodTest() { <span style='color: blue; font-weight: bold'>using (ShimsContext.Create()) // ShimsContext가 Shim 객체 생성되는 AppDomain이 아님!</span> { AppDomain domain = AppDomain.CreateDomain("Test", new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence), new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase }); domain.DoCallBack(() => { <span style='color: blue; font-weight: bold'>ShimDateTime.NowGet = () => new DateTime(2015, 05, 06);</span> <span style='color: blue; font-weight: bold'>Assert.IsTrue(DateTime.Now.Year == 2015); // Assert 발생!!!</span> } ); AppDomain.Unload(domain); } } </pre> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9973
(왼쪽의 숫자를 입력해야 합니다.)