猿问

动态创建任意对象进行单元测试

我们的代码库充满了具有大量数字和字符串字段的域对象。在我的单元测试中,我发现自己手动创建了这些不同对象的实例,如下所示:


var car1 = new Car();

car1.Make = "Make1";

car1.Model = "Model1";

car1.Year = 1;


var car2 = new Car();

car2.Make = "Make2";

car2.Model = "Model2";

car2.Year = 2;

等等..


只用一个函数调用自动构建任何类型的对象的最干净的方法是什么?


请记住,我不希望使用随机值生成字段。任意可重复值(如上面的 1 和 2)是我想要的。


墨色风雨
浏览 257回答 2
2回答

眼眸繁星

既然是域对象,为什么不使用工厂呢?无论您设置什么值,单元测试都应该是有效的。如果要测试特定值,则应编写特定测试。或者可能是您的需求不同?

BIG阳

我最终选择了一种通用方法,该方法使用反射来自动完成问题显示的手动完成。以这种方式创建的任何字段都可以被覆盖,并且必须手动设置任何复杂类型。布尔值只是设置为真。我很想听听人们对这样做的想法。当我构建单元测试时,它为我节省了大量的时间和精力。这是我的测试最终的外观(简单示例,没有我想用特定值覆盖的复杂类型或字符串/数字字段,尽管这些也被处理):[Test]public void TestCreateCars(){&nbsp; &nbsp; // Arrange&nbsp; &nbsp; var expectedCars = new List<Car>();&nbsp; &nbsp; expectedCars.Add(TestUtils.CreateObject<Car>(1));&nbsp; &nbsp; expectedCars.Add(TestUtils.CreateObject<Car>(2));&nbsp; &nbsp; expectedCars.Add(TestUtils.CreateObject<Car>(3));&nbsp; &nbsp; // using moq but anything could be used&nbsp; &nbsp; _carService.Setup(x => x.GetNewCarsInfo()).Returns(cars);&nbsp; &nbsp; var carsFactory = new carFactory(_carService);&nbsp; &nbsp; // Act&nbsp; &nbsp; var cars = carsFactory.CreateCars();&nbsp; &nbsp; // Assert&nbsp; &nbsp; Assert.AreEqual(3, cars.Count);&nbsp; &nbsp; TestUtils.AssertObjectDefaultFields(cars[0], 1);&nbsp; &nbsp; TestUtils.AssertObjectDefaultFields(cars[1], 2);&nbsp; &nbsp; TestUtils.AssertObjectDefaultFields(cars[2], 3);&nbsp; &nbsp; _carService.VerifyAll();}创建对象的方法:// Use this to instantiate objects of types with a large number of int/string fields for testing.&nbsp; You can override any values after calling this - complex fields won't be set//&nbsp; -sets numeric values on that object to its 'id'//&nbsp; -sets string values on that object to the name of the field concatenated with the 'id'public static T CreateObject<T>(int id){&nbsp; &nbsp; var instance = (T)Activator.CreateInstance(typeof(T));&nbsp; &nbsp; // only set properties with a visible setter&nbsp; &nbsp; var properties = typeof(T).GetProperties().Where(prop => prop.GetSetMethod() != null);&nbsp; &nbsp; foreach (var property in properties)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var type = property.PropertyType;&nbsp; &nbsp; &nbsp; &nbsp; if (IsNumeric(type))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = Nullable.GetUnderlyingType(type) ?? type;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var value = Convert.ChangeType(id, type);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.SetValue(instance, value);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; else if (property.PropertyType == typeof(string))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.SetValue(instance, property.Name + id);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; else if (property.PropertyType == typeof(bool))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.SetValue(instance, true);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return instance;}断言以这种方式创建的对象的方法具有预期值:// Use this to assert that an object created by CreateObject<T> has all of the 'default' values://&nbsp; -numeric values equal its 'id'//&nbsp; -string values equal the name of the field concatenated with the 'id'//&nbsp;// unsetProperties: property names that we want to assert are their default values// ignoreProperties: property names that we don't want to assert anything against - we should assert against these outside of this methodpublic static void AssertObjectDefaultFields<T>(T obj, int id, HashSet<string> unsetProperties = null, HashSet<string> ignoreProperties = null){&nbsp; &nbsp; // only test properties with a visible setter, otherwise it wouldnt have been set&nbsp; &nbsp; var properties = typeof(T).GetProperties().Where(prop => prop.GetSetMethod() != null);&nbsp; &nbsp; unsetProperties = unsetProperties ?? new HashSet<String>();&nbsp; &nbsp; ignoreProperties = ignoreProperties ?? new HashSet<String>();&nbsp; &nbsp; foreach (var property in properties)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if(!ignoreProperties.Contains(property.Name))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (unsetProperties.Contains(property.Name))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Assert.AreEqual(defaultValue, property.GetValue(obj));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (IsNumeric(property.PropertyType))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Assert.AreEqual(id, property.GetValue(obj));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (property.PropertyType == typeof(string))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Assert.AreEqual(property.Name + id, property.GetValue(obj));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (property.PropertyType == typeof(bool))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Assert.AreEqual(true, property.GetValue(obj));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}用于测试字段的类型是否为数字:private static bool IsNumeric(Type type){&nbsp; &nbsp; var NumericTypes = new HashSet<Type>&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; typeof(int),&nbsp; typeof(double),&nbsp; typeof(decimal),&nbsp; &nbsp; &nbsp; &nbsp; typeof(long), typeof(short),&nbsp; &nbsp;typeof(sbyte),&nbsp; &nbsp; &nbsp; &nbsp; typeof(byte), typeof(ulong),&nbsp; &nbsp;typeof(ushort),&nbsp; &nbsp; &nbsp; &nbsp; typeof(uint), typeof(float)&nbsp; &nbsp; };&nbsp; &nbsp; return NumericTypes.Contains(Nullable.GetUnderlyingType(type) ?? type);}
随时随地看视频慕课网APP
我要回答