使用 NUnit 对 app.config 文件进行单元测试

61

当您在单元测试依赖于app.config文件中的值的应用程序时,您如何测试这些值是否被正确读取以及您的程序如何响应配置文件中输入的不正确值?

修改NUnit应用程序的配置文件是荒谬的,但我无法读取要测试的app.config中的值。

编辑:也许我应该澄清一下。我不担心ConfigurationManager无法读取这些值,但我关心的是测试我的程序如何响应读取到的值。

13个回答

46

我通常会将类似读取配置文件的外部依赖项隔离到自己的门面类中,门面类功能很少。在测试中,我可以创建该类的模拟版本并使用它来代替真正的配置文件。您可以创建自己的模拟版本或使用像moq或rhino mocks这样的框架。

这样,您就可以轻松地尝试不同的配置值而无需编写首先编写xml配置文件的复杂测试。读取配置的代码通常非常简单,因此需要进行的测试很少。


4
很可怕看到这个答案没有得到更多的赞,而其他关于添加/阅读/编辑配置文件的答案却有很高的分数。对于读者来说,这个答案是一个正确的方向,可以让您的单元测试简单且符合SOLID原则。 - Sébastien Sevrin
1
“没有什么问题是通过增加另一层抽象化无法解决的。” - Iain
2
@lain “除了过多的抽象层次问题” :) - Igand

31

您可以在测试设置中在运行时修改配置部分。例如:

// setup
System.Configuration.Configuration config = 
     ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.Sections.Add("sectionname", new ConfigSectionType());
ConfigSectionType section = (ConfigSectionType)config.GetSection("sectionname");
section.SomeProperty = "value_you_want_to_test_with";
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("sectionname");

// carry out test ...

当然,您可以设置自己的辅助方法来更优雅地完成此操作。


1
从磁盘读取文件会使整个测试套件变成集成测试。因为存在全局状态和使用外部依赖项,所以您不再进行单元测试。 - sara

27

您可以调用 ConfigurationManager.AppSettings 的 set 方法来设置特定单元测试所需的值。

[SetUp]
public void SetUp()
{
  ConfigurationManager.AppSettings.Set("SettingKey" , "SettingValue");
  // rest of unit test code follows
}

当单元测试运行时,它将使用这些值来运行代码


2
这绝对是我解决问题最简单的方法。我的代码只需检查名为“Environment”的键是否为TEST或LIVE。因此,我只需在单元测试方法的开头将该键设置为TEST即可。 - Erik L

16

您可以使用 ConfigurationManager 类读取和写入 app.config 文件。


2
哦,那么我可以在ConfigurationManager集合中设置值吗?我总是以为它是只读的。我想这就是我做出假设的结果:P - Dana
3
我试过了,非常有效!例如:ConfigurationManager.AppSettings["SomeKey"] = "MockValue";。好答案! - Scott Rippey
当我尝试这样做时,它确实更改了您指定的键的值,但也删除了其他所有键和值 :( - Yasser Shaikh
@Yasser:Mendelt的解决方案更好 ;) - Steven A. Lowe
@StevenA.Lowe 我最终使用了红色的 Pervez Choudhury 的解决方案 :) - Yasser Shaikh

11

我在处理web.config时遇到了类似的问题... 我找到了一个有趣的解决方案。您可以封装配置读取功能,例如像这样:

public class MyClass {

public static Func<string, string> 
     GetConfigValue = s => ConfigurationManager.AppSettings[s];

//...

}

然后通常使用

string connectionString = MyClass.GetConfigValue("myConfigValue");

但在单元测试中,可以通过初始化来"覆盖"该函数,如下所示:

MyClass.GetConfigValue = s =>  s == "myConfigValue" ? "Hi", "string.Empty";

更多相关信息:

http://rogeralsing.com/2009/05/07/the-simplest-form-of-configurable-dependency-injection/


3
一种更加优雅的解决方案是在配置设置本身上使用简单的依赖注入。在我看来,这比必须模拟配置读取类/包装器等更加清晰。
例如,假设一个名为“Weather”的类需要“ServiceUrl”才能正常工作(例如,它调用Web服务以获取天气)。而不是有一行代码主动去配置文件获取该设置(无论该代码是在Weather类中还是在可以像其他响应一样被模拟的单独配置读取器中),Weather类可以允许将该设置注入,可以通过构造函数的参数或可能通过属性设置器注入。这样,单元测试非常简单直接,甚至不需要进行模拟。
然后可以使用控制反转(或依赖注入)容器来注入设置的值,因此Weather类的消费者不需要明确地从某个地方提供该值,因为容器已经处理了。

2

这对我有效:

 public static void BasicSetup()
  {
     ConnectionStringSettings connectionStringSettings = 
          new ConnectionStringSettings();
     connectionStringSettings.Name = "testmasterconnection";
     connectionStringSettings.ConnectionString = 
          "server=localhost;user=some;database=some;port=3306;";
     ConfigurationManager.ConnectionStrings.Clear();
     ConfigurationManager.ConnectionStrings.Add(connectionStringSettings);
  }

1

1
你可以始终将读取位包装在接口中,并有一个特定的实现从配置文件中读取。然后,您将使用模拟对象编写测试,以查看程序如何处理错误值。 就个人而言,我不会测试此特定实现,因为这是.NET Framework代码(并且我假设 - 希望 - 微软已经对其进行了测试)。

0

嗯,我刚刚遇到了同样的问题... 我想要测试一个被网站引用的业务逻辑项目。 但我只想测试业务逻辑。所以在测试项目的预建事件中,我将 app.Config 文件复制到 bin\debug 文件夹中,并从 app.config 中引用它们...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接