【问题标题】:Manipulating the app.config file for unit tests为单元测试操作 app.config 文件
【发布时间】:2011-11-29 01:08:12
【问题描述】:

我已将我的 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>

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

【问题讨论】:

  • 你能更好地解释一下用例吗?为什么需要在运行时更改 tests.dll.config?
  • 这个问题和这个问题一模一样:stackoverflow.com/questions/168931/…
  • 好的,我猜它可以关闭了。谢谢指点。
  • 有完整源代码的最终解决方案吗?

标签: c# .net nunit


【解决方案1】:

我建议使用属性 useHostsFile 和 importFile 实现接口 IConfig。然后,我将删除对该文件的所有直接依赖项,但实现 IConfig 的类 ConfigDefault 除外。在此实现中,您加载正常的配置文件。对于每个测试,您都可以实现另一个类,该类也继承自 IConfig。我建议使用依赖注入。 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
       }
    

    【讨论】:

      【解决方案3】:

      这是我对这个挑战的两分钱。简单地说,创建一个新类 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=")]

      【讨论】:

      • 这很棒。如果可以的话,我会投票 10 次。谢谢!
      • 注:构造函数中的赋值可以简化(感谢VS的推荐):_setting1 = _setting1 ?? : Properties.Settings.Default.Setting1;
      【解决方案4】:

      我有一个案例,我的配置阅读器是使用惰性单例模式实现的,只读取一次。这样更改 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 的示例

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-02
        • 2010-09-15
        • 1970-01-01
        • 2011-04-03
        • 2012-05-20
        • 2013-09-22
        • 1970-01-01
        相关资源
        最近更新 更多