简体   繁体   English

单元测试 AppContext 开关

[英]Unit Testing AppContext Switches

Change Log更改日志

  • Renamed question: AppContext Switch Failing In Unit Test (CryptographicException: Invalid Algorithm Specified) 重命名问题: AppContext Switch 在单元测试中失败(CryptographicException: Invalid Algorithm Specified)
  • Added section at bottom explaining the issue in further detail after doing more investigating在进行更多调查后,在底部添加了更详细地解释问题的部分
  • Added section at the bottom explaining a workaround I have implemented在底部添加了解释我已实施的解决方法的部分

I have a piece of code that I am writing unit tests for that signs some xml.我有一段代码,我正在为它编写单元测试来标记一些 xml。

I noticed when developing the code that .NET 4.7.1+ will throw CryptographicException: Invalid algorithm specified .我在开发代码时注意到 .NET 4.7.1+ 会抛出CryptographicException: Invalid algorithm specified To get around this issue I used the following AppContext switches (sources: wiktor zychla , why am i getting invalid algorithm specified... ).为了解决这个问题,我使用了以下AppContext开关(来源: wiktor zychla为什么我指定的算法无效... )。

I've put the following code in any app that calls my xml signing code:我已将以下代码放在任何调用我的 xml 签名代码的应用程序中:

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

When running in the wild, this code is functioning correctly, and my xml is being signed as expected.在野外运行时,此代码运行正常,并且我的 xml 正在按预期签名。

I've written some unit tests for this code, it's my first real attempt at writing unit tests and I'm experiencing some weird edge cases.我已经为这段代码编写了一些单元测试,这是我第一次真正尝试编写单元测试,我遇到了一些奇怪的边缘情况。

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

So if the exception is thrown, I display a nice message saying please add the following code before calling this method.所以如果抛出异常,我会显示一条很好的消息,说请在调用此方法之前添加以下代码。

My test class looks something like this:我的测试类看起来像这样:

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

When I run SignXML_AppContextSwitchSet_Success which I'd expect to pass on its own, the test passes everytime (just me repeatedly running it).当我运行我希望自己通过的SignXML_AppContextSwitchSet_Success ,测试每次都通过(只是我反复运行它)。 If I clean my solution and run all tests, the test always fails.如果我清理我的解决方案并运行所有测试,测试总是失败。

My xml generation class has no shared objects, it could infact be static.我的 xml 生成类没有共享对象,它实际上可能是静态的。

The test fails because the cryptographic exception gets thrown (triggering my code and throwing the exception telling the dev (me) to add the app context switch).测试失败,因为加密异常被抛出(触发我的代码并抛出异常告诉开发人员(我)添加应用程序上下文切换)。

Is there a way to verify that the config switches are being respected?有没有办法验证配置开关是否受到尊重?

When running the code from my console tester app and in the wpf app I've built it into, the exception is never thrown.从我的控制台测试器应用程序和我内置的 wpf 应用程序运行代码时,永远不会抛出异常。 It is only thrown when I run all of the unit tests back to back.只有当我背靠背运行所有单元测试时才会抛出它。 I would like these tests to run on an azure pipeline so it is important that it can run simultaneously as well as individually.我希望这些测试在 azure 管道上运行,因此它可以同时运行以及单独运行非常重要。

My thoughts on this exception are that maybe when I'm running all the tests together, the AppContext switches are not being honoured.我对这个例外的想法是,也许当我一起运行所有测试时, AppContext开关没有得到尊重。 I've tried to add checks before signing the xml:我尝试在签署 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");
}

Now you'd expect this code to fail my fail test (where the switches aren't set) and it does, you can see my crude NotImplementedException message in the test explorer.现在,您希望此代码无法通过我的失败测试(未设置开关),并且确实如此,您可以在测试资源管理器中看到我粗略的NotImplementedException消息。 However the test i'm expecting to succeed throws a CryptographicException again...然而,我期望成功的测试再次抛出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.

As I said earlier, I'm very new to testing and I'm not sure if my approach to test design is wrong, if I'm just testing something that's spaghetti code under the hood so maybe I'm just doomed trying to test it or theres something I'm missing to allow correct testing of this case.正如我之前所说,我对测试很陌生,我不确定我的测试设计方法是否错误,如果我只是在测试一些底层代码,所以也许我注定要尝试测试它或我缺少某些东西以允许正确测试此案例。

I'm currently using NUnit 3.12.0 + NUnit3TestAdapter 3.15.1 to write tests, although I was intially using MSTest 1.3.2 and experienced the same behaviour using both frameworks.我目前正在使用NUnit 3.12.0 + NUnit3TestAdapter 3.15.1来编写测试,尽管我最初使用的是MSTest 1.3.2并且在使用这两个框架时遇到了相同的行为。


Update更新

I've discovered that the execution order of the following tests determines the success:我发现以下测试的执行顺序决定了成功:

  • SignXML_AppContextSwitchNotSet_Fail SignXML_AppContextSwitchNotSet_Fail
  • SignXML_AppContextSwitchSet_Success SignXML_AppContextSwitchSet_Success

I discovered this by reading posts such as ( Unit test fails when running all, but passes when running individually ) on tests failing when not running individually.我通过阅读诸如( 单元测试在运行所有时失败,但在单独运行时通过)之类的帖子中发现了这一点,这些帖子在未单独运行时失败的测试中。

It got me thinking about the values being set, I want the following cases:它让我考虑设置的值,我想要以下情况:

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

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

Eventhough I have already tested for these values being set correctly, are the underlying switches being updated?尽管我已经测试过这些值的设置是否正确,但底层开关是否正在更新? How can I cleanup these underlying objects?如何清理这些底层对象?


Workaround / 'Nasty' Fix解决方法/“讨厌”修复

I found the following issue on NUnit's GitHub (appdomain per test (isolation per test), for dealing with static classes) .我在NUnit 的 GitHub (appdomain per test (isolation per test), for processing static classes)上发现了以下问题。 In this thread they discuss the option of a new AppDomain per test, this is exactly what I need in my case.在这个线程中,他们讨论了每个测试的新AppDomain选项,这正是我所需要的。

A ways down the thread jnm2 posts a util class to run code in a seperate AppDomain ( code ) (I won't post it here since this post's already massive). jnm2发布了一个 util 类来在单独的AppDomain ( code ) 中运行代码(我不会在这里发布它,因为这篇文章已经很大了)。 I took this util class and adapted my tests to work with it (you can't use any class variables or methods, meaning I had to copy my helper methods code).我使用了这个 util 类并调整了我的测试以使用它(您不能使用任何类变量或方法,这意味着我必须复制我的辅助方法代码)。

Once I had implemented the util and adapted my tests to fit its criteria, the tests started passing both when I ran them individually & all at once.一旦我实现了 util 并调整了我的测试以符合它的标准,当我单独运行它们时,测试开始通过,并且一次全部通过。

Mission accomplished!任务完成!

I had exactly the same problem!我遇到了完全相同的问题! I came across this post and it motivated my solution!我遇到了这篇文章,它激发了我的解决方案!

Instead of running the tests in the separate AppDomain, I've ended up modifying the production code where the calls to the cryptographic library are made.我没有在单独的 AppDomain 中运行测试,而是修改了调用加密库的生产代码。 I run the problematic calls in a separate AppDomain where I set the switches before proceeding.我在一个单独的 AppDomain 中运行有问题的调用,我在继续之前设置了开关。

This fixes all the tests as well as it enables the clients of my library not to mess with the switches.这修复了所有测试以及它使我的库的客户端不会弄乱开关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM