Управление файлом app.config для модульных тестов - PullRequest
6 голосов
/ 01 октября 2011

Я выделил тесты NUnit для моего приложения на C # в сборке под названием Tests.dll.Связанный файл конфигурации называется Tests.dll.config.Это то, что использует Nunit, а не фактический файл конфигурации моего приложения.Выглядит это так (только показывает несколько параметров конфигурации, а их гораздо больше):

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <appSettings>
    <add key="useHostsFile" value="true" />
    <add key="importFile" value="true" />

   </appSettings>
</configuration>

Чтобы обеспечить тщательное тестирование моего приложения, мне потребуется изменить параметры конфигурации между тестами.После того, как я выполнил пару тестов, я хотел бы добавить некоторые новые значения конфигурации в файл и использовать их в последующих тестах.Какой код мне нужно добавить, чтобы сделать это?

Ответы [ 4 ]

3 голосов
/ 02 октября 2011

Рекомендую реализовать интерфейс IConfig со свойствами useHostsFile и importFile. Тогда я бы удалил все прямые зависимости с этим файлом, кроме класса ConfigDefault, который реализует IConfig В этой реализации вы загружаете ваш обычный файл конфигурации. Для каждого теста вы можете реализовать другой класс, который также наследуется от IConfig. Я предлагаю использовать инъекцию зависимости. Ninject бесплатен и прост в использовании.

2 голосов
/ 05 апреля 2013

Я использую этот код:

 [TestMethod]
    public void Test_general()
    {
        var cs = new ConnectionStringSettings();
        cs.Name = "ConnectionStrings.Oracle";
        cs.ConnectionString = "DATA SOURCE=xxx;PASSWORD=xxx;PERSIST SECURITY INFO=True;USER ID=xxx";
        cs.ProviderName = "Oracle.DataAccess.Client";

        var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        //config.ConnectionStrings.ConnectionStrings.Clear();
        config.ConnectionStrings.ConnectionStrings.Remove(cs.Name);
        config.ConnectionStrings.ConnectionStrings.Add(cs);
        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection("connectionStrings");

        // your code for your test here
   }
1 голос
/ 11 марта 2016

Вот мои два цента на этот вызов. Просто создайте новый класс AppSettings в качестве уровня абстракции. При обычных операциях он просто читает настройки из файла конфигурации приложения. Но модульные тесты могут переопределять настройки для каждого потока, что позволяет выполнять модульные тесты параллельно с другими настройками.

internal sealed class AppSettings
{
    private static readonly AppSettings instance;
    private static ConcurrentDictionary<int, AppSettings> threadInstances;
    private string _setting1;
    private string _setting2;

    static AppSettings() { instance = new AppSettings(); }

    internal AppSettings(string setting1 = null, string setting2 = null) {
        _setting1 = setting1 != null ? setting1 : Properties.Settings.Default.Setting1;
        _setting2 = setting2 != null ? setting2 : Properties.Settings.Default.Setting2;
    }

    internal static AppSettings Instance {
        get {
            if (threadInstances != null) {
                AppSettings threadInstance;
                if (threadedInstances.TryGetValue(Thread.CurrentThread.ManagedThreadId, out threadInstance)) {
                    return threadInstance;
                }
            }
            return instance;
        }

        set {
            if (threadInstances == null) {
                lock (instance) {
                    if (threadInstances == null) {
                        int numProcs = Environment.ProcessorCount;
                        int concurrencyLevel = numProcs * 2;
                        threadInstances = new ConcurrentDictionary<int, AppSettings>(concurrencyLevel, 5);
                    }
                }
            }

            if (value != null) {
                threadInstances.AddOrUpdate(Thread.CurrentThread.ManagedThreadId, value, (key, oldValue) => value);
            } else {
                AppSettings threadInstance;
                threadInstances.TryRemove(Thread.CurrentThread.ManagedThreadId, out threadInstance);
            }
        }
    }

    internal static string Setting1 => Instance._setting1;

    internal static string Setting2 => Instance._setting2;
}

В коде приложения получить доступ к настройкам, используя статические свойства:

function void MyApplicationMethod() {
    string setting1 = AppSettings.Setting1;
    string setting2 = AppSettings.Setting2;
}

В модульных тестах опционально отменять выбранные настройки:

[TestClass]
public class MyUnitTest
{
    [TestCleanup]
    public void CleanupTest()
    {
        //
        // Clear any app settings that were applied for the current test runner thread.
        //
        AppSettings.Instance = null;
    }

    [TestMethod]
    public void MyUnitMethod()
    {
        AppSettings.Instance = new AppSettings(setting1: "New settings value for current thread");
        // Your test code goes here
    }
}

ПРИМЕЧАНИЕ. Поскольку все методы класса AppSettings объявлены как внутренние, необходимо сделать их видимыми для сборки модульного теста, используя атрибут: [assembly: InternalsVisibleTo ("<имя сборки>, PublicKey = <открытый ключ>")]

0 голосов
/ 22 февраля 2019

У меня был случай, когда мои считыватели конфигурации были реализованы с использованием шаблона Lazy singleton для чтения только один раз. Такого изменения app.config было недостаточно, поскольку значение уже было прочитано из исходного файла конфигурации.

Однако синглтон не пересекает границы доменов приложений, и вы можете указать app.config для новых создаваемых доменов приложений. Таким образом, я смог протестировать настройки приложения Lazy Singleton с помощью:

        var otherAppDomainSetup = new AppDomainSetup
        {
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
            DisallowBindingRedirects = false,
            DisallowCodeDownload = true,
            ConfigurationFile = Path.Combine(AppContext.BaseDirectory, "Artifacts\\Configs\\OtherApp.config")
        };

        var otherAppDomain = AppDomain.CreateDomain(friendlyName: "Other", securityInfo: null, info: otherAppDomainSetup);

        otherAppDomain.DoCallBack(new CrossAppDomainDelegate(() => Assert.AreEqual(expected: ***, actual: ***static singleton call***)));
        AppDomain.Unload(otherAppDomain);

Для нестатических вызовов см. Пример использования CreateInstanceAndUnwrap в https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7.2

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...