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

发布于 2024-12-07 20:01:43 字数 485 浏览 0 评论 0原文

我已将 C# 应用程序的 NUnit 测试隔离在名为 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>

为了确保我的应用程序经过彻底测试,我需要在测试之间更改配置选项。 运行几次测试后,我想向文件添加一些新的配置值,并供后续测试使用。我需要添加什么代码才能做到这一点?

I have isolated the NUnit tests for my C# app in an assembly called Tests.dll. The associated configuration file is called Tests.dll.config. This is what Nunit uses rather than my app's actual config file. It looks like this (only showing a couple of config options there are lots more):

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

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

   </appSettings>
</configuration>

To ensure my app is thoroughly tested, I will need to change config options between tests.
After I have run a couple of tests, I would like to add some new config values to the file and have these used by subsequent tests. What code would I need to add do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

骑趴 2024-12-14 20:01:44

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

但是,单例不会跨越应用程序域边界,您可以为您创建的新应用程序域指定 app.config。因此,我能够通过以下方式测试惰性单例应用程序设置:

        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://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7.2

I had a case where my config readers were implemented using Lazy singleton pattern to read only once. Such that changing the app.config was not sufficient as the value was already read from the original configuration file.

The singleton does not cross appdomain boundaries however, and you can specify the app.config for new app domains that you create. So I was able to test the Lazy singleton app settings with:

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

For non-static calls see the example of using CreateInstanceAndUnwrap at https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7.2

甜嗑 2024-12-14 20:01:43

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

I recommend to implement a interface IConfig with properties useHostsFile and importFile. Then i would remove all direct dependecies to this file except in the Class ConfigDefault which implements IConfig. In this implementation you load your normal config file. For each test you can implement another Class which also inherits from IConfig. I suggest to use a Dependecy Injection. Ninject is free and easy to use.

南…巷孤猫 2024-12-14 20:01:43

我使用这段代码:

 [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
   }

I use this code:

 [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
   }
萌逼全场 2024-12-14 20:01:43

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

Here is my two cents to this challenge. Simply, create a new class AppSettings as an abstraction layer. Under normal operations it will simply read the settings from the application configuration file. But unit tests can override the settings on a per thread basis allowing for unit tests to execute in parallel with different settings.

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

In the application code, access settings using the static properties:

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

In unit tests, optionally override selected settings:

[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
    }
}

NOTE: As all methods of the AppSettings class are declared as internal it is necessary to make them visible to the unit test assembly using the attribute:
[assembly: InternalsVisibleTo("<assembly name>, PublicKey=<public key>")]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文