Відповіді:
Я вважаю, що один із стандартних підходів до цього - використовувати фасадну схему, щоб обернути диспетчер конфігурації, і тоді у вас є щось слабко пов'язане, над чим ви маєте контроль.
Отже, ви обмотаєте 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 різних кластери.
Ви можете використовувати shims для зміни 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.
}
}
Ви можете прочитати докладнішу інформацію про лайків і підробок за адресою: Ізолюючи код під тестом за допомогою 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
Джошуа Енфілд може бути занадто високим, і ви можете пропустити помилки, які існують через такі речі, як неправильний аналіз значення конфігурації. З іншого боку, використання ConfigurationManager.AppSettings
прямо, як підказує LosManos, є занадто великою деталізацією щодо впровадження, не кажучи вже про те, що це може мати побічні ефекти на інших тестах і не може бути використане в паралельних тестових програмах без ручної синхронізації (як NameValueConnection
це не безпечно для потоків).
Це статична властивість, і Moq призначений для методів або класів екземплярів Moq, які можна знущатися через успадкування. Іншими словами, Moq тут не допоможе вам.
Для глузування зі статики я використовую інструмент під назвою Moles , який є безкоштовним. Є й інші інструменти для ізоляції рамок, наприклад Typemock, які також можуть це зробити, хоча я вважаю, що це платні інструменти.
Що стосується статики та тестування, інший варіант - створити статичний стан самостійно, хоча це часто може бути проблематичним (як, я думаю, це було б у вашому випадку).
І, нарешті, якщо рамки ізоляції не є варіантом, і ви прихильні до цього підходу, фасад, згаданий Джошуа, є хорошим підходом, або будь-яким підходом взагалі, коли ви визначаєте клієнтський код цього від ділової логіки, яку ви Ви використовуєте для тестування.
Я думаю, що написання власного постачальника послуг app.config - це просте завдання і корисніше за все інше. Особливо слід уникати будь-яких підробок, таких як прокладки тощо, тому що як тільки ви їх використовуєте, редагування та продовження більше не працює.
Провайдери, якими я користуюся, виглядають приблизно так:
За замовчуванням вони отримують значення, 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; } }
}