.net測試篇之測試神器Autofixture Generator使用與自定義builder

系列目錄html

有了上一節自定義配置,不少問題都能解決了,可是若是僅僅是爲了解決一個簡單問題那麼建立一個類顯得有點繁重.其實AutoFixture在建立Fixture對象時有不少方便的Fluent配置,咱們這裏介紹一些比較經常使用了.數據庫

建立對象是忽略一些屬性

有些時候有這樣的一些業務場景,有些字段是非必填項,可是一旦填寫則必須符合指定規則.這些非必填字段在業務中僅僅當它存在的時候作一些校驗,其它地方並無使用到它.這樣在單元測試的時候咱們爲了效率能夠暫時忽略這些字段.在後面集成測試的時候再提供完整數據.數組

下面看看AutoFixture在生成對象的時候如何顯式地忽略一些字段dom

之因此要忽略是由於若是不忽略AutoFixture自動爲字符串類型生成一個guid字符串,這將會致使驗證失敗.函數

咱們擴展一下Person類,代碼以下單元測試

public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime BirthDay { get; set; }
        public string Mobile { get; set; }
        public string IDCardNo { get; set; }
        public string Email { get; set; }
    }

咱們看配置代碼測試

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();

            var psn = fix.Build<Person>().
                Without(a => a.IDCardNo).
                Create();
        }

這裏fix對象使用Build方法,生成一個自定義生成對象,而後會出現不少自定義配置方法,咱們使用without方法指示AutoFixture在生成時不生成某一字段,一個without後面還能夠再接一個,若是須要忽略其它字段,能夠串聯使用多個without.ui

指定當前時間

在集成測試的時候,有些關於時間的字段都須要是當前時間,這時候可使用AutoFixture內置的自定義類CurrentDateTimeGenerator來實現設計

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new CurrentDateTimeGenerator());
            var psn = fix.Create<Person>();
        }

[info]固然以上配置多是沒有必要的,由於C#很早就支持屬性賦初值了.調試

UriGenerator

此配置可讓AutoFixture生成一個Uri
```cs
[Test]
public void FixValueTest()
{
var fix = new Fixture();
fix.Customizations.Add(new UriGenerator());
var br = fix.Create ()# AutoFixture配置二
有了上一節自定義配置,不少問題都能解決了,可是若是僅僅是爲了解決一個簡單問題那麼建立一個類顯得有點繁重.其實AutoFixture在建立Fixture對象時有不少方便的Fluent配置,咱們這裏介紹一些比較經常使用了.

建立對象是忽略一些屬性

有些時候有這樣的一些業務場景,有些字段是非必填項,可是一旦填寫則必須符合指定規則.這些非必填字段在業務中僅僅當它存在的時候作一些校驗,其它地方並無使用到它.這樣在單元測試的時候咱們爲了效率能夠暫時忽略這些字段.在後面集成測試的時候再提供完整數據.

下面看看AutoFixture在生成對象的時候如何顯式地忽略一些字段

之因此要忽略是由於若是不忽略AutoFixture自動爲字符串類型生成一個guid字符串,這將會致使驗證失敗.

咱們擴展一下Person類,代碼以下

public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime BirthDay { get; set; }
        public string Mobile { get; set; }
        public string IDCardNo { get; set; }
        public string Email { get; set; }
    }

咱們看配置代碼

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();

            var psn = fix.Build<Person>().
                Without(a => a.IDCardNo).
                Create();
        }

這裏fix對象使用Build方法,生成一個自定義生成對象,而後會出現不少自定義配置方法,咱們使用without方法指示AutoFixture在生成時不生成某一字段,一個without後面還能夠再接一個,若是須要忽略其它字段,能夠串聯使用多個without.

指定當前時間

在集成測試的時候,有些關於時間的字段都須要是當前時間,這時候可使用AutoFixture內置的自定義類CurrentDateTimeGenerator來實現

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new CurrentDateTimeGenerator());
            var psn = fix.Create<Person>();
        }

[info]固然以上配置多是沒有必要的,由於C#很早就支持屬性賦初值了.

UriGenerator

此配置可讓AutoFixture生成一個Uri

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new UriGenerator());
            var br = fix.Create<Uri>();
        }

MailAddressGenerator

用於生成郵箱地址

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new MailAddressGenerator());
            var mr = fix.Create<MailAddress>()
        }

固然不少時候咱們是想要MailAddress裏的字符串.這時候使用MailAddress的Address屬性便可.

;
}
```

MailAddressGenerator

用於生成郵箱地址

[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new MailAddressGenerator());
            var mr = fix.Create<MailAddress>()
        }

固然不少時候咱們是想要MailAddress裏的字符串.這時候使用MailAddress的Address屬性便可.

AutoFixture結合DataAnnotations

有些時候有這樣一些場影,咱們的實體類中有不少驗證註解,這就對製造出的假數據有不少要求,好比必須符合郵箱號,身份證號,字符串長度必須爲特定值,手機號必須爲特定長度數字等等.經過前面講到的自定義配置咱們能夠實現以上功能,可是若是僅僅是爲了生成一個指定長度的字符串咱們再新建一個配置類實在有點繁瑣,而且這些邏輯也並不是都是通用的.更爲複雜的是有時候一個特定字段必須符合某一正則規則,若是這個規則很是複雜想要生成知足它的假數據確實須要花費些心思,這時候若是AutoFixture能自動生成知足條件的假數據那該有好多,實際上AutoFixture確實能夠生成知足DataAnnotations約束的字段,而且不須要配置默認就是支持的.

好比如今Person類改成以下:

public class Person{
        [StringLength(10)]
        public string Name { get; set; }
        [Range(18,42)]
        public int Age { get; set; }
        public DateTime BirthDay { get; set; }
        [RegularExpression("\\d{11}")]
        public string Mobile { get; set; }
}

AutoFixture會自動生成知足條件的字段.

AutoFixture 生成符合特定業務的字段.

Attribute自動生成符合註解約束的字段爲集成測試提供了很大方便.然而一些功能不管是註解仍是AutoFixture內置的配置都沒法完成,這時候就須要自定義的配置.

好比說有如下業務場景,有些業務模型帶有開始時間和結束時間,這裏就有一個隱性約束就是結束時間必須大於或者等於開始時間,若是不是這樣數據庫中就沒法取到值.這個時候就必須使用自定義配置了.

好比有一下模型:

public class CustomDate
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }

[info]這裏只是一個普通例子,不少時候業務裏面都有這樣的模型,若是要讓結束時間晚於開始時間,咱們首先要先肯定哪一個時開始時間,哪一個是結束時間,這裏咱們使用基於命名約束的方法來肯定它們:即開始時間帶有start,結束時間帶有end(固然也能夠是其它標識,只要能肯定它們便可).固然這並非一種很好的設計.理想的狀況下是對字段進行註解,可是僅僅爲了測試而去擴展示有項目的作法也是值得商榷的.

如下爲自定義方法

public class DateTimeSpecimenBuilder:ISpecimenBuilder
    {
        private readonly Random _random = new Random();
        private DateTime startDate = DateTime.Now;
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as PropertyInfo;
            if (pi != null && pi.Name.ToLower().Contains("start") &&
                (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?)))
            {
               
                var stDate = context.Create<DateTime>();
                
                startDate =stDate ;
                return startDate;
            }

            if (pi != null && pi.Name.ToLower().Contains("end") &&
                (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?)))
            {
                var endDate = startDate.AddDays(_random.Next(1,20));
                return endDate;
            }
            return new NoSpecimen();
        }
    }
[Test]
        public void FixValueTest()
        {
            var fix = new Fixture();
            fix.Customizations.Add(new DateTimeSpecimenBuilder());
            var customDate = fix.Create<CustomDate>();
        }

簡單梳理一下以上代碼,基本邏輯就是若是傳入字段包含start關鍵特徵而且是日期類型,咱們就把它的值存起來,當後面遇到包含end特徵的屬性時就把剛纔存入的值再加上指定天數這樣就能保證enddate大於startdate了.

生成引用類型時指定構造函數

當一個類有多個構造函數時,AutoFixture默認使用參數最少的構造函數來構造一個對象,可是這在有些時候會形成麻煩:一個Bll類可能有多個構造函數,構造函數裏傳入的是依賴對象,若是隻調用參數最少的構造函數則不少依賴沒法傳入,這樣若是使用到了依賴對象就會報Null引用異常.這個時候咱們就須要顯式的指定調用哪個構造函數.

咱們仍然經過示例來說解.

咱們把Person類改爲以下:

public class Person
    {
        public Person(string name)
        {
            Name = name;
        }

        public Person(string name,int age)
        {
            Age = age;
            Name = name;
        }
        [StringLength(10)]
        public string Name { get; set; }
        [Range(18,42)]
        public int Age { get; set; }
        public DateTime BirthDay { get; set; }
        [RegularExpression("\\d{11}")]
        public string Mobile { get; set; }
        public string IDCardNo { get; set; }

與以前相比,這個類多了兩個有參構造函數.

注意,即便類型不包含無參構造函數,AutoFixture依然可以建立它,只是使用哪一個構造函數是不肯定的.

要實現讓AutoFixture選擇咱們想要的構造函數,咱們建立一個實現了IMethodQuery的類型,而後添加到配置裏.

這個類代碼以下

public class MyMethodSelector : IMethodQuery
    {
        private readonly Type[] _types;

        public MyMethodSelector(params Type[] type)
        {
            _types = type;
        }

        public IEnumerable<IMethod> SelectMethods(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException();
            }

            var constructors = type
                .GetConstructors().Where(a => a.GetParameters().Select(t => t.ParameterType).SequenceEqual(_types))
                .Select(a => new ConstructorMethod(a));

            return constructors;
        }
    }

咱們來分析一下這段代碼,首先咱們在構造函數裏傳入type 數組,這裏的type爲構造函數參數的類型.SelectMethods爲接口提供的方法,這個方法接收一個type類型做爲參數,這個type爲操做的對象的類型.而後咱們使用GetConstructors方法獲取它全部的構造函數.而後經過Where過濾符合條件的(條件是參數的類型和構造函數傳入的類型同樣).而後咱們使用過濾後的Constructorinfo來建立一個ConstructorMethod,ConstructorMethod爲AutoFixture提供的一個類型.

下面是測試代碼

var fix = new Fixture();
            fix.Customize(new ConstructorCustomization(typeof(Person),
                new MyMethodSelector(typeof(string), typeof(int))));
            var psn = fix.Create<Person>();

這裏咱們給MyMethodSelector傳入了兩個類型,分別是string類型和int類型,以指望AutoFixture調用包含string和int參數的構造方法.咱們啓用調試模式,就能夠看到Person的第二個構造函數被調用了.

看到這裏,不少人可能會感受有些厭煩,感受這樣作還不如直接New一個對象,在new的時候顯式調用特定的構造函數就不會有這麼麻煩了.關於直接new對象的缺點前面也說過,若是Bll層有變更,則須要顯式修改測試方法,不利於維護,而且這個方法是能夠通用的.一旦建立好以後之後遇到這樣的業務就能夠直接調用了.

相關文章
相關標籤/搜索