답변:
이에 대한 한 가지 표준 접근 방식 은 구성 관리자를 래핑 하기 위해 파사드 패턴 을 사용하고 느슨하게 결합되어 제어 할 수있는 것입니다.
따라서 ConfigurationManager를 래핑합니다. 다음과 같은 것 :
public class Configuration: IConfiguration
{
public User
{
get
{
return ConfigurationManager.AppSettings["User"];
}
}
}
(구성 클래스에서 인터페이스를 추출한 다음 코드의 모든 곳에서 해당 인터페이스를 사용할 수 있습니다.) 그런 다음 IConfiguration을 모의합니다. 몇 가지 다른 방법으로 파사드 자체를 구현할 수 있습니다. 위에서 저는 개별 속성을 래핑하기로 선택했습니다. 또한 약한 형식의 해시 배열이 아닌 강력한 형식의 정보를 사용하는 부수적 인 이점도 얻을 수 있습니다.
var configurationMock = new Mock<IConfiguration>();
및 설정을위한 :configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
AspnetMvc4를 사용하고 있습니다. 조금 전에 나는 썼다
ConfigurationManager.AppSettings["mykey"] = "myvalue";
내 테스트 방법에서 완벽하게 작동했습니다.
설명 : 테스트 메소드는 일반적으로 web.config
또는 에서 가져온 앱 설정이있는 컨텍스트에서 실행됩니다 myapp.config
. ConfigurationsManager
이 응용 프로그램 전역 개체에 도달하여 조작 할 수 있습니다.
하지만 : 테스트를 병렬로 실행하는 테스트 러너가있는 경우 이것은 좋은 생각이 아닙니다.
ConfigurationManager.AppSettings
는 NameValueCollection
스레드로부터 안전하지 않으므로 적절한 동기화없이 병렬 테스트를 사용하는 것은 어쨌든 좋은 생각이 아닙니다. 그렇지 않으면 당신은 ConfigurationManager.AppSettings.Clear()
당신의 TestInitialize
/ ctor를 부르면 당신은 황금색입니다.
수행해야 할 작업이 아닐 수도 있지만 테스트 프로젝트에서 app.config를 사용하는 것을 고려 했습니까? 따라서 ConfigurationManager는 app.config에 입력 한 값을 가져 오므로 아무것도 모의 할 필요가 없습니다. 이 솔루션은 "변수"구성 파일을 테스트 할 필요가 없기 때문에 내 요구에 적합합니다.
Web.config
프로젝트를 포함하는 항목 에서 가져옵니다 . 테스트 중에에서 잘 알려진 값을 가져 오는 app.config
것은 매우 유효합니다. 단위 테스트는 "cluster1"이 작동한다고 풀 때 조건이 작동하는지 확인하기 만하면됩니다. 이 경우에는 4 개의 다른 클러스터 만 있습니다.
shim을 사용 AppSettings
하여 사용자 지정 NameValueCollection
개체 를 수정할 수 있습니다 . 다음은이를 달성 할 수있는 방법의 예입니다.
[TestMethod]
public void TestSomething()
{
using(ShimsContext.Create()) {
const string key = "key";
const string value = "value";
ShimConfigurationManager.AppSettingsGet = () =>
{
NameValueCollection nameValueCollection = new NameValueCollection();
nameValueCollection.Add(key, value);
return nameValueCollection;
};
///
// Test code here.
///
// Validation code goes here.
}
}
shim 및 fake에 대한 자세한 내용은 Isolating Code Under Test with Microsoft Fakes에서 확인할 수 있습니다. 도움이 되었기를 바랍니다.
조롱하는 대신 스터 빙을 고려해 보셨습니까? AppSettings
속성은이다 NameValueCollection
:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
// Arrange
var settings = new NameValueCollection {{"User", "Otuyh"}};
var classUnderTest = new ClassUnderTest(settings);
// Act
classUnderTest.MethodUnderTest();
// Assert something...
}
}
public class ClassUnderTest
{
private readonly NameValueCollection _settings;
public ClassUnderTest(NameValueCollection settings)
{
_settings = settings;
}
public void MethodUnderTest()
{
// get the User from Settings
string user = _settings["User"];
// log
Trace.TraceInformation("User = \"{0}\"", user);
// do something else...
}
}
이점은 구현이 간단하고 실제로 필요할 때까지 System.Configuration에 대한 종속성이 없다는 것입니다.
IConfiguration
으로 Joshua Enfield가 제안한대로 구성 관리자를 래핑하는 것은 너무 높은 수준 일 수 있으며 잘못된 구성 값 구문 분석 등으로 인해 존재하는 버그를 놓칠 수 있습니다. 반면에 ConfigurationManager.AppSettings
LosManos가 제안한대로 직접 사용 하는 것은 구현 세부 사항이 너무 많으며 다른 테스트에 부작용이있을 수 있으며 수동 동기화없이 병렬 테스트 실행에 사용할 수 없습니다 ( NameValueConnection
스레드 안전하지 않음).
이것은 정적 속성이며 Moq는 상속을 통해 모의 될 수있는 Moq 인스턴스 메서드 또는 클래스로 설계되었습니다. 다시 말해, Moq는 여기서 당신에게 어떤 도움도되지 않을 것입니다.
모의 통계를 위해 무료 인 Moles 라는 도구를 사용합니다 . 유료 도구라고 생각하지만 Typemock과 같은 다른 프레임 워크 격리 도구도 있습니다.
정적 및 테스트와 관련하여 또 다른 옵션은 정적 상태를 직접 만드는 것입니다.하지만 이것은 종종 문제가 될 수 있습니다 (귀하의 경우에 해당한다고 생각합니다).
그리고 마지막으로 격리 프레임 워크가 선택 사항이 아니고이 접근 방식에 전념한다면 Joshua가 언급 한 파사드가 좋은 접근 방식이거나 일반적으로 클라이언트 코드를 비즈니스 논리에서 제외하는 모든 접근 방식입니다. 테스트에 사용하고 있습니다.
자신의 app.config 공급자를 작성하는 것은 간단한 작업이며 다른 것보다 더 유용하다고 생각합니다. 특히 shim 등과 같은 가짜는 피해야합니다. 사용하자마자 Edit & Continue가 더 이상 작동하지 않기 때문입니다.
내가 사용하는 공급자는 다음과 같습니다.
기본적으로 그들은 값을 얻지 App.config
만 단위 테스트의 경우 모든 값을 재정의하고 각 테스트에서 독립적으로 사용할 수 있습니다.
인터페이스가 필요하지 않거나 매번 반복해서 구현할 필요가 없습니다. 나는 유틸리티 dll을 가지고 있으며 많은 프로젝트와 단위 테스트 에서이 작은 도우미를 사용합니다.
public class AppConfigProvider
{
public AppConfigProvider()
{
ConnectionStrings = new ConnectionStringsProvider();
AppSettings = new AppSettingsProvider();
}
public ConnectionStringsProvider ConnectionStrings { get; private set; }
public AppSettingsProvider AppSettings { get; private set; }
}
public class ConnectionStringsProvider
{
private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get
{
string customValue;
if (_customValues.TryGetValue(key, out customValue))
{
return customValue;
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[key];
return connectionStringSettings == null ? null : connectionStringSettings.ConnectionString;
}
}
public Dictionary<string, string> CustomValues { get { return _customValues; } }
}
public class AppSettingsProvider
{
private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get
{
string customValue;
return _customValues.TryGetValue(key, out customValue) ? customValue : ConfigurationManager.AppSettings[key];
}
}
public Dictionary<string, string> CustomValues { get { return _customValues; } }
}