单元测试AppContext开关

时间:2019-12-10 12:16:18

标签: .net unit-testing nunit signedxml cryptographicexception

更改日志

  • 重命名的问题: AppContext开关在单元测试中失败(CryptographicException:指定了无效的算法)
  • 在底部进行了添加,并在进行了更多调查之后进一步详细说明了该问题
  • 在底部添加的部分说明了我已实施的解决方法

我有一段代码正在编写用于标记xml的单元测试。

在开发代码时,我注意到.NET 4.7.1+将抛出CryptographicException: Invalid algorithm specified。为了解决这个问题,我使用了以下AppContext开关(来源:wiktor zychlawhy am i getting invalid algorithm specified...)。

我已将以下代码放入任何调用xml签名代码的应用程序中:

AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true);

在野外运行时,此代码正常运行,并且我的xml正按预期方式签名。

我已经为该代码编写了一些单元测试,这是我第一次真正尝试编写单元测试,并且遇到了一些奇怪的情况。

try
{
    // Compute the signature.
    signedXml.ComputeSignature();
}
catch (CryptographicException cex)
{
    //Contains instead of == since this message contains newline characters...
    if (cex.Message.Contains("Invalid algorithm specified"))
    {
        string code = Environment.NewLine + "AppContext.SetSwitch(\"Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms\", true);" +
                        Environment.NewLine + "AppContext.SetSwitch(\"Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms\", true); ";

        throw new Exception($"Cryptographic exception was thrown, ensure that the following switches are set in the executing assembly: {code}", cex);
    }
    else
    {
        throw cex;
    }
}

因此,如果引发了异常,那么我会显示一条很好的消息,说请在调用此方法之前添加以下代码。

我的测试课看起来像这样:

[TestFixture]    
public class XMLSigningTests
{
    protected IXMLSigner _xmlSigner;
    protected byte[] _publicKey;
    protected string _password;

    [SetUp]
    public void Init()
    {
        _xmlSigner = new XMLSigner();
        _password = "test";
        _publicKey = GenerateTestCert(_password);
    }

    [TearDown]
    public void CleanUp()
    {
        _xmlSigner = null;
        _publicKey = null;
        _password = null;
    }

    [Test]
    public void SignXML_AppContextSwitchNotSet_Fail()
    {
        XMLDocument xmlDoc = GenerateXML();

        Assert.Throws<Exception>(() => { _xmlSigner.SignXML(xmlDoc, _publicKey, _password); });
    }

    [Test]
    public void SignXML_AppContextSwitchSet_Success()
    {
        AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
        AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true);

        XMLDocument xmlDoc = GenerateXML();

        var signedXml = _xmlSigner.SignXML(xmlDoc, _publicKey, _password); 

        Assert.IsFalse(signedXML == null);
        //More validation here
    }

    private XMLDocument GenerateXML()
    {
        return new XMLDocument("Some fancy xml here...");
    }

    private byte[] GenerateTestCert(string password)
    {
        //Use Pluralsight.Crypto to generate a self signed cert & export as .pfx
        return cert.Export(X509ContentType.Pfx, password);
    }
}

当我运行自己希望通过的SignXML_AppContextSwitchSet_Success时,每次都会通过测试(只是我反复运行它)。如果我清洗解决方案并运行所有测试,则测试始终会失败。

我的xml生成类没有共享对象,它实际上可能是静态的。

测试失败是因为抛出了加密异常(触发我的代码并抛出异常告诉开发人员(我)添加应用上下文开关)。

有没有办法验证配置开关是否受到尊重?

在我的控制台测试器应用程序和我内置的wpf应用程序中运行代码时,永远不会引发异常。仅当我背对背运行所有单元测试时才抛出该错误。我希望这些测试可以在蔚蓝的管道上运行,因此它必须同时运行也可以分别运行很重要。

我对这个异常的想法是,也许当我同时运行所有测试时,AppContext开关没有被兑现。我已经尝试在签署xml之前添加检查:

AppContext.TryGetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", out bool xmlSwitch);
AppContext.TryGetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", out bool pkcsSwitch);

if (!xmlSwitch || !pkcsSwitch)
{
    throw new NotImplementedException("THESE VALUES SHOULD BE SET WTF");
}

现在,您希望该代码无法通过我的失败测试(未设置开关),并且确实失败了,您可以在测试浏览器中看到我粗略的NotImplementedException消息。但是,我期望测试会成功再次抛出CryptographicException ...

System.Exception : Cryptographic exception was thrown, ensure that the following switches are set in the executing assembly: 
    AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
    AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true); 
      ----> System.Security.Cryptography.CryptographicException : Invalid algorithm specified.

正如我之前所说,我对测试非常陌生,我不确定我的测试设计方法是否错误,如果我只是在后台测试意大利面条式代码,那么也许我注定要失败尝试对其进行测试,或者缺少我所允许的正确测试方法。

尽管我最初使用NUnit 3.12.0 + NUnit3TestAdapter 3.15.1并在两个框架中都经历了相同的行为,但我目前正在使用MSTest 1.3.2编写测试。


更新

我发现以下测试的执行顺序决定了成功:

  • SignXML_AppContextSwitchNotSet_Fail
  • SignXML_AppContextSwitchSet_Success

我通过阅读(Unit test fails when running all, but passes when running individually)之类的帖子来发现这一点,因为它们在不单独运行时测试失败。

这让我想到了要设置的值,我想要以下几种情况:

//Test I expect to fail
AppContext.SetSwitch("the switch..", false);

//Test I expect to pass
AppContext.SetSwitch("the switch..", true);

尽管我已经测试了这些值的设置是否正确,但是底层开关是否正在更新?如何清除这些基础对象?


解决方法/“讨厌”修复程序

我在NUnit's GitHub (appdomain per test (isolation per test), for dealing with static classes)上发现了以下问题。他们在该线程中讨论了每个测试使用新AppDomain的选项,这正是我所需要的。

在线程jnm2下的一种方式发布了一个util类,以在单独的AppDomaincode)中运行代码(由于这篇文章已经很大了,所以我不会在这里发布它)。我接受了这个util类,并对其测试进行了修改以使其可以使用(您不能使用任何类变量或方法,这意味着我必须复制我的辅助方法代码)。

一旦我实现了util并调整了我的测试以使其符合标准,当我分别运行它们并一次运行它们时,这些测试就开始通过。

任务完成!

1 个答案:

答案 0 :(得分:0)

我遇到了完全相同的问题! 我碰到了这篇文章,这激发了我的解决方案!

我没有在单独的AppDomain中运行测试,而是最终修改了对密码库进行调用的生产代码。 我在单独的AppDomain中运行有问题的呼叫,然后在继续操作之前在其中设置了开关。

这不仅修复了所有测试,而且使我的库的客户端不会弄乱开关。