当处理基类和派生类时,处理AppSettings的最佳方式是什么,这将允许可测试性

本文关键字:处理 是什么 方式 可测试性 最佳 AppSettings 基类 派生 | 更新日期: 2023-09-27 18:08:26

我希望这是一个体面的问题,背景是我有一个抽象基类,这个基类有一些具体的方法实现,可以访问派生类。基类和所有派生类都有一组唯一的变量。所有常见的功能都在基类的具体方法中(这些方法不是虚拟的,所以它们不能被覆盖)。每个派生类也从App.Config文件中的AppSettings中获得一些这些值。

所以我的问题是,哪里是最好的地方,把这些const值,将遵循最佳实践,并允许代码是适当的可测试的(这最后一块是非常重要的)?

我知道的两个选项是:

1)创建一个静态配置类,其中包含每个类的所有值。这对于正常的const来说是很好的,但是检索App.Config值将失败,除非测试项目在自己的App.Config文件中有这些值(对我来说这似乎是一个肮脏的hack)

2)另一种选择是声明它们并在using类中获得值设置,这坚持在使用它的地方声明它原则,但它使类不可测试,除非您再次将值添加到测试项目的App.Config中。

public abstract class BaseClassA
{
    private const string PATH = "'ParentClassPath'" // Option 2
    private int _var1;
    private string _var2;
    public BaseClassA(int param1, string param2)
    {
        _var1 = param1;
        _var2 = param2;
    }
    public int Param1Prop { get; private set;}
    public string Param2Prop { get; private set; }
    protected string Method1(string value1, string value2, string value3)
    {
        Directory.CreateDirectory(StaticClass.PATH); //Option 1 
        return Path.GetDirectoryName(PATH) //Option 2
    }
}
public class DerivedClassB
             : base(1, "param2")
{
    private const string PATH = "'DerivedClassPath'" // Option 2
    public BaseClassA(int param1, string param2)
    {
        _var1 = param1;
        _var2 = param2;
    }
    public int Param1Prop { get; private set;}
    public string Param2Prop { get; private set; }
    protected string Method1(string value1, string value2, string value3)
    {
        Directory.CreateDirectory(StaticClass.DERIVED_PATH); //Option 1 
        return Path.GetDirectoryName(PATH) //Option 2
    }
}

当处理基类和派生类时,处理AppSettings的最佳方式是什么,这将允许可测试性

创建一个自定义类来封装对ConfigurationManager的调用。AppSettings["key"]带有一个接口。在测试中模拟该接口,以便可以定义需要测试的值。要解释的粗略示例(未经测试):

public interface IConfigurationService
{
    string Get(string key);
}

public class ConfigurationService :IConfigurationService
{
    public string Get(string key)
    {
        return ConfigurationManager.AppSettings[key];
    }
}

在您的测试中,您现在可以这样模拟您的接口:

public void NaiveTest()
{
    var key = "someKey";
    var result = "someValue";
    var mockConfigurationService = new Mock<IConfigurationService>();
    mockConfigurationService.Setup(x => x.Get(key)).Returns(result);
   // pass service to class being tested and continue
}

在基类中包含配置服务,然后每个派生类可以根据需要提取它们的值。现在可以通过使用mock测试任何值。更多链接:https://github.com/moq/moq4

如果您需要进一步的建议,请告诉我。

从测试的角度来看,这听起来像是Fakes的工作。您可以添加一个shim来拦截读取配置值的调用,然后您可以在测试中编写多个可能的值,而根本不需要配置文件。

这是一篇关于它们如何工作的文章。https://msdn.microsoft.com/en-us/library/hh549175.aspx

这里有一个部分的例子,如果你试图从注册表拦截读取,它将如何在你的单元测试中看起来:(我没有任何使用配置文件的例子,但你得到的想法。)

using (ShimsContext.Create())
{
    Microsoft.Win32.Fakes.ShimRegistryKey.AllInstances.GetValueString = (key, valueName) =>
    { return "SomeValue"; };
    Microsoft.Win32.Fakes.ShimRegistryKey.AllInstances.OpenSubKeyStringBoolean = (key, subkey, write) =>
    {
        var openKey = new Microsoft.Win32.Fakes.ShimRegistryKey();
        openKey.NameGet = () => Path.Combine(key.Name, subkey);
        return openKey;
    };
    // Exercise the code under test that reads from the registry here. 
    // Make Assertions
}

所以在这个例子中,当代码试图调用注册表来打开注册表项时,它会运行OpenSubKeyStringBoolean lambda。然后,当它对返回的键调用GetValue时,它会运行GetValueString lambda。所以结果是"SomeValue"被返回,而不是在注册表项(或不是)。

使用ShimsContext中的任何内容都运行您的shim代码。一旦ShimsContext被处理掉,一切就会恢复正常。