操作app.config文件进行单元测试

问题描述 投票:6回答:4

我在一个名为Tests.dll的程序集中为我的C#app隔离了NUnit测试。关联的配置文件名为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>

为确保我的应用程序经过全面测试,我需要在测试之间更改配置选项。在我运行了几个测试之后,我想在文件中添加一些新的配置值,并将这些值用于后续测试。我需要添加什么代码呢?

c# .net nunit
4个回答
3
投票

我建议使用属性useHostsFile和importFile实现一个接口IConfig。然后,除了在实现IConfig的Class ConfigDefault中,我将删除所有直接依赖此文件。在此实现中,您加载正常的配置文件。对于每个测试,您可以实现另一个继承自IConfig的类。我建议使用Dependecy Injection。 Ninject免费且易于使用。


2
投票

我用这个代码:

 [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
投票

这是我对这一挑战的两分钱。简单地说,创建一个新的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类的所有方法都声明为internal,因此必须使用以下属性使它们对单元测试程序集可见:[assembly:InternalsVisibleTo(“<assembly name>,PublicKey = <public key>”)]


0
投票

我有一个案例,我的配置阅读器使用Lazy单例模式实现只读一次。因此,更改app.config是不够的,因为已从原始配置文件中读取该值。

但是,单例不会跨越appdomain边界,您可以为您创建的新应用程序域指定app.config。所以我能够用以下方法测试Lazy单例应用程序设置:

        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);

对于非静态调用,请参阅在https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7.2上使用CreateInstanceAndUnwrap的示例

© www.soinside.com 2019 - 2024. All rights reserved.