programacion pasaje parametros parameter opcionales metodos metodo funciones firma definicion c# unit-testing autofixture automocking

pasaje - parametros opcionales c#



AutoFixture.AutoMoq proporciona un valor conocido para un parámetro de constructor (5)

Así que estoy seguro de que la gente podría elaborar la implementación generalizada de la sugerencia de Mark, pero pensé que la publicaría para comentarios.

He creado un ParameterNameSpecimenBuilder genérico basado en el LifeSpanArg de Mark:

public class ParameterNameSpecimenBuilder<T> : ISpecimenBuilder { private readonly string name; private readonly T value; public ParameterNameSpecimenBuilder(string name, T value) { // we don''t want a null name but we might want a null value if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException("name"); } this.name = name; this.value = value; } public object Create(object request, ISpecimenContext context) { var pi = request as ParameterInfo; if (pi == null) { return new NoSpecimen(request); } if (pi.ParameterType != typeof(T) || !string.Equals( pi.Name, this.name, StringComparison.CurrentCultureIgnoreCase)) { return new NoSpecimen(request); } return this.value; } }

Luego, he definido un método genérico de extensión IFixture en IFixture que establece la personalización:

public static class FreezeByNameExtension { public static void FreezeByName<T>(this IFixture fixture, string name, T value) { fixture.Customizations.Add(new ParameterNameSpecimenBuilder<T>(name, value)); } }

La siguiente prueba ahora pasará:

[TestMethod] public void FreezeByName_Sets_Value1_And_Value2_Independently() { //// Arrange IFixture arrangeFixture = new Fixture(); string myValue1 = arrangeFixture.Create<string>(); string myValue2 = arrangeFixture.Create<string>(); IFixture sutFixture = new Fixture(); sutFixture.FreezeByName("value1", myValue1); sutFixture.FreezeByName("value2", myValue2); //// Act TestClass<string> result = sutFixture.Create<TestClass<string>>(); //// Assert Assert.AreEqual(myValue1, result.Value1); Assert.AreEqual(myValue2, result.Value2); } public class TestClass<T> { public TestClass(T value1, T value2) { this.Value1 = value1; this.Value2 = value2; } public T Value1 { get; private set; } public T Value2 { get; private set; } }

Acabo de comenzar a usar AutoFixture.AutoMoq en mis pruebas de unidad y me resulta muy útil para crear objetos en los que no me importa el valor específico. Después de todo, la creación de objetos anónimos es de lo que se trata.

Con lo que estoy luchando es cuando me preocupo por uno o más de los parámetros del constructor. Tome ExampleComponent continuación:

public class ExampleComponent { public ExampleComponent(IService service, string someValue) { } }

Quiero escribir una prueba en la que someValue un valor específico para someValue pero dejo que el Servicio IService sea ​​creado automáticamente por AutoFixture.AutoMoq .

Sé cómo usar Freeze en mi IFixture para mantener un valor conocido que se inyectará en un componente, pero no puedo ver cómo suministrar un valor conocido por mi cuenta.

Esto es lo que idealmente me gustaría hacer:

[TestMethod] public void Create_ExampleComponent_With_Known_SomeValue() { // create a fixture that supports automocking IFixture fixture = new Fixture().Customize(new AutoMoqCustomization()); // supply a known value for someValue (this method doesn''t exist) string knownValue = fixture.Freeze<string>("My known value"); // create an ExampleComponent with my known value injected // but without bothering about the IService parameter ExampleComponent component = this.fixture.Create<ExampleComponent>(); // exercise component knowning it has my known value injected ... }

Sé que podría hacerlo llamando directamente al constructor, pero esto ya no sería creación anónima de objetos. ¿Hay alguna manera de usar AutoFixture.AutoMock de esta manera o necesito incorporar un contenedor DI en mis pruebas para poder hacer lo que quiero?

EDITAR:

Probablemente debería haber sido menos absract en mi pregunta original, así que aquí está mi escenario específico.

Tengo una interfaz ICache que tiene TryRead<T> genéricos TryRead<T> y Write<T> :

public interface ICache { bool TryRead<T>(string key, out T value); void Write<T>(string key, T value); // other methods not shown... }

Estoy implementando un CookieCache donde ITypeConverter maneja la conversión de objetos hacia y desde cadenas y se usa la lifespan para establecer la fecha de caducidad de una cookie.

public class CookieCache : ICache { public CookieCache(ITypeConverter converter, TimeSpan lifespan) { // usual storing of parameters } public bool TryRead<T>(string key, out T result) { // read the cookie value as string and convert it to the target type } public void Write<T>(string key, T value) { // write the value to a cookie, converted to a string // set the expiry date of the cookie using the lifespan } // other methods not shown... }

Así que cuando escribo una prueba para la fecha de caducidad de una cookie, me importa la vida útil, pero no tanto sobre el convertidor.


Esta parece ser la solución más completa aquí. Así que voy a añadir la mía:

Lo primero en crear ISpecimenBuilder que puede manejar múltiples parámetros de constructor

internal sealed class CustomConstructorBuilder<T> : ISpecimenBuilder { private readonly Dictionary<string, object> _ctorParameters = new Dictionary<string, object>(); public object Create(object request, ISpecimenContext context) { var type = typeof (T); var sr = request as SeededRequest; if (sr == null || !sr.Request.Equals(type)) { return new NoSpecimen(request); } var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(); if (ctor == null) { return new NoSpecimen(request); } var values = new List<object>(); foreach (var parameter in ctor.GetParameters()) { if (_ctorParameters.ContainsKey(parameter.Name)) { values.Add(_ctorParameters[parameter.Name]); } else { values.Add(context.Resolve(parameter.ParameterType)); } } return ctor.Invoke(BindingFlags.CreateInstance, null, values.ToArray(), CultureInfo.InvariantCulture); } public void Addparameter(string paramName, object val) { _ctorParameters.Add(paramName, val); } }

Luego crea un método de extensión que simplifique el uso del constructor creado

public static class AutoFixtureExtensions { public static void FreezeActivator<T>(this IFixture fixture, object parameters) { var builder = new CustomConstructorBuilder<T>(); foreach (var prop in parameters.GetType().GetProperties()) { builder.Addparameter(prop.Name, prop.GetValue(parameters)); } fixture.Customize<T>(x => builder); } }

Y uso:

var f = new Fixture(); f.FreezeActivator<UserInfo>(new { privateId = 15, parentId = (long?)33 });


Me di cuenta que @Nick ya casi estaba allí. Cuando se reemplaza el argumento del constructor, debe ser para el tipo dado y estar limitado a ese tipo solamente.

Primero creamos un nuevo ISpecimenBuilder que mira el "Member.DeclaringType" para mantener el alcance correcto.

public class ConstructorArgumentRelay<TTarget,TValueType> : ISpecimenBuilder { private readonly string _paramName; private readonly TValueType _value; public ConstructorArgumentRelay(string ParamName, TValueType value) { _paramName = ParamName; _value = value; } public object Create(object request, ISpecimenContext context) { if (context == null) throw new ArgumentNullException("context"); ParameterInfo parameter = request as ParameterInfo; if (parameter == null) return (object)new NoSpecimen(request); if (parameter.Member.DeclaringType != typeof(TTarget) || parameter.Member.MemberType != MemberTypes.Constructor || parameter.ParameterType != typeof(TValueType) || parameter.Name != _paramName) return (object)new NoSpecimen(request); return _value; } }

A continuación, creamos un método de extensión para permitirnos conectarlo fácilmente con AutoFixture.

public static class AutoFixtureExtensions { public static IFixture ConstructorArgumentFor<TTargetType, TValueType>( this IFixture fixture, string paramName, TValueType value) { fixture.Customizations.Add( new ConstructorArgumentRelay<TTargetType, TValueType>(paramName, value) ); return fixture; } }

Ahora creamos dos clases similares para probar.

public class TestClass<T> { public TestClass(T value1, T value2) { Value1 = value1; Value2 = value2; } public T Value1 { get; private set; } public T Value2 { get; private set; } } public class SimilarClass<T> { public SimilarClass(T value1, T value2) { Value1 = value1; Value2 = value2; } public T Value1 { get; private set; } public T Value2 { get; private set; } }

Finalmente, lo probamos con una extensión de la prueba original para ver que no anulará los argumentos de constructor con nombre y tipo similares.

[TestFixture] public class AutoFixtureTests { [Test] public void Can_Create_Class_With_Specific_Parameter_Value() { string wanted = "This is the first string"; string wanted2 = "This is the second string"; Fixture fixture = new Fixture(); fixture.ConstructorArgumentFor<TestClass<string>, string>("value1", wanted) .ConstructorArgumentFor<TestClass<string>, string>("value2", wanted2); TestClass<string> t = fixture.Create<TestClass<string>>(); SimilarClass<string> s = fixture.Create<SimilarClass<string>>(); Assert.AreEqual(wanted,t.Value1); Assert.AreEqual(wanted2,t.Value2); Assert.AreNotEqual(wanted,s.Value1); Assert.AreNotEqual(wanted2,s.Value2); } }


Podrías hacer algo como esto. Imagine que desea asignar un valor particular al argumento de TimeSpan llamado lifespan .

public class LifespanArg : ISpecimenBuilder { private readonly TimeSpan lifespan; public LifespanArg(TimeSpan lifespan) { this.lifespan = lifespan; } public object Create(object request, ISpecimenContext context) { var pi = request as ParameterInfo; if (pi == null) return new NoSpecimen(request); if (pi.ParameterType != typeof(TimeSpan) || pi.Name != "lifespan") return new NoSpecimen(request); return this.lifespan; } }

Imperativamente, podría ser usado así:

var fixture = new Fixture(); fixture.Customizations.Add(new LifespanArg(mySpecialLifespanValue)); var sut = fixture.Create<CookieCache>();

Este enfoque puede generalizarse hasta cierto punto, pero al final, estamos limitados por la falta de una forma muy tipográfica de extraer un ParameterInfo de un constructor o argumento de método en particular.


Tienes que reemplazar:

string knownValue = fixture.Freeze<string>("My known value");

con:

fixture.Inject("My known value");

Puedes leer más sobre Inject here .

En realidad, el método de extensión Freeze hace:

var value = fixture.Create<T>(); fixture.Inject(value); return value;

Lo que significa que la sobrecarga que usó en la prueba en realidad llamó Create<T> con una semilla: Mi valor conocido da como resultado "Mi valor conocido4d41f94f-1fc9-4115-9f29-e50bc2b4ba5e" .