Можно ли смоделировать сбой теста NUnit без изменения кода каждого теста и / или прибора? - PullRequest
1 голос
/ 20 марта 2019

Я хотел бы иметь возможность смоделировать неудачу произвольного теста, чтобы проверить, правильно ли работает моя логика TearDown.Модульное тестирование на модульном тесте, если хотите.

Но на самом деле у меня есть TearDown в некоторых приборах, который выдает вложения при сбоях.Я должен быть в состоянии показать эту функцию, но трудно вызвать сбой, когда вам это больше всего нужно.

Итак, я хочу создать параметр теста, указывающий имя теста, который я хочу выполнить.Теперь я могу легко написать атрибут, реализующий IWrapTestMethod или IApplyToContext, но затем мне нужно применить его к каждому методу тестирования.

Есть ли способ реализовать его, не затрагивая каждый тест и / или прибор?Каким-либо атрибутом уровня сборки или методом настройки уровня сборки, который будет запускаться перед каждым тестом?

Крайне важно, чтобы эта логика не помешала запуску методов TearDown, поэтому ITestActionисключение из BeforeTest не соответствует требованиям.

Можно ли это сделать?

1 Ответ

1 голос
/ 21 марта 2019

Я нашел решение:

using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Reflection;

[assembly: Common.EnableFailureSimulation]

namespace Common
{
    public class SimulateFailureMethodInfoWrapper : IMethodInfo
    {
        private readonly IMethodInfo m_mi;

        public SimulateFailureMethodInfoWrapper(IMethodInfo mi)
        {
            m_mi = mi;
        }

        public ITypeInfo TypeInfo => m_mi.TypeInfo;

        public MethodInfo MethodInfo => m_mi.MethodInfo;

        public string Name => m_mi.Name;

        public bool IsAbstract => m_mi.IsAbstract;

        public bool IsPublic => m_mi.IsPublic;

        public bool ContainsGenericParameters => m_mi.ContainsGenericParameters;

        public bool IsGenericMethod => m_mi.IsGenericMethod;

        public bool IsGenericMethodDefinition => m_mi.IsGenericMethodDefinition;

        public ITypeInfo ReturnType => m_mi.ReturnType;

        public T[] GetCustomAttributes<T>(bool inherit) where T : class => m_mi.GetCustomAttributes<T>(inherit);

        public Type[] GetGenericArguments() => m_mi.GetGenericArguments();

        public IParameterInfo[] GetParameters() => m_mi.GetParameters();

        public object Invoke(object fixture, params object[] args)
        {
            var res = m_mi.Invoke(fixture, args);
            Assert.Fail("Failure simulation");
            return res;
        }

        public bool IsDefined<T>(bool inherit) where T : class => m_mi.IsDefined<T>(inherit);

        public IMethodInfo MakeGenericMethod(params Type[] typeArguments) => m_mi.MakeGenericMethod(typeArguments);
    }

    [AttributeUsage(AttributeTargets.Assembly)]
    public class EnableFailureSimulationAttribute : Attribute, ITestAction
    {
        private static string s_failTestMethod = GetParameterByName("!");

        public ActionTargets Targets => ActionTargets.Test;

        public void AfterTest(ITest test)
        {
        }

        public void BeforeTest(ITest test)
        {
            if (test.MethodName == s_failTestMethod && test is Test testImpl)
            {
                testImpl.Method = new SimulateFailureMethodInfoWrapper(testImpl.Method);
                s_failTestMethod = "!";
            }
        }
    }
}

Альтернативным подходом было бы использование Moq и макет интерфейса IMethodInfo вместо реального класса SimulateFailureMethodInfoWrapper.

В любом случае, это, кажется, работает отлично.

...