Birim testi için uygulamanızı yalıtmak için dolguları kullanma

Microsoft Fakes Framework tarafından kullanılan iki önemli teknolojiden biri olan dolgu türleri, test sırasında uygulamanızın bileşenlerini yalıtmada etkili olur. Çağrıları keserek ve belirli yöntemlere yönlendirerek çalışırlar ve bunu testinizde özel koda yönlendirebilirsiniz. Bu özellik, dış koşullardan bağımsız olarak her çağrı sırasında sonuçların tutarlı ve tahmin edilebilir olmasını sağlayarak bu yöntemlerin sonucunu yönetmenizi sağlar. Bu denetim düzeyi test sürecini kolaylaştırır ve daha güvenilir ve doğru sonuçlar elde edilmesine yardımcı olur.

Kodunuz ve çözümünüzün bir parçasını oluşturmayan derlemeler arasında bir sınır oluşturmanız gerektiğinde dolguları kullanın. Amacınız çözümünüzün bileşenlerini birbirinden yalıtmak olduğunda saplamaların kullanılması önerilir.

(Saplamalar için daha ayrıntılı bir açıklama için bkz. Birim testi için uygulamanızın parçalarını birbirinden yalıtmak için saplamaları kullanın.)

Dolgu sınırlamaları

Dolguların sınırlamaları olduğuna dikkat etmek önemlidir.

Dolgular .NET temel sınıfındaki belirli kitaplıklardan, özellikle .NET Framework'teki mscorlib ve System'den ve .NET Core veya .NET 5+ içindeki System.Runtime'dan tüm türlerde kullanılamaz. Başarılı ve etkili bir test stratejisi sağlamak için test planlama ve tasarım aşamasında bu kısıtlama dikkate alınmalıdır.

Dolgu Oluşturma: Adım Adım Kılavuz

Bileşeninizin çağrısı içerdiğini System.IO.File.ReadAllLinesvarsayalım:

// Code under test:
this.Records = System.IO.File.ReadAllLines(path);

Sınıf Kitaplığı Oluşturma

  1. Visual Studio'yu açma ve proje oluşturma Class Library

    Screenshot of NetFramework Class Library project in Visual Studio.

  2. Proje adını ayarlama HexFileReader

  3. Çözüm adını ShimsTutorialayarlayın.

  4. Projenin hedef çerçevesini .NET Framework 4.8 olarak ayarlayın

  5. Varsayılan dosyayı silme Class1.cs

  6. Yeni bir dosya HexFile.cs ekleyin ve aşağıdaki sınıf tanımını ekleyin:

    // HexFile.cs
    public class HexFile
    {
        public string[] Records { get; private set; }
    
        public HexFile(string path)
        {
            this.Records = System.IO.File.ReadAllLines(path);
        }
    }
    

Test Projesi Oluşturma

  1. Çözüme sağ tıklayın ve yeni bir proje ekleyin MSTest Test Project

  2. Proje adını ayarlama TestProject

  3. Projenin hedef çerçevesini .NET Framework 4.8 olarak ayarlayın

    Screenshot of NetFramework Test project in Visual Studio.

Fakes Derlemesi Ekle

  1. 'a proje başvurusu ekleme HexFileReader

    Screenshot of the command Add Project Reference.

  2. Fakes Derlemesi Ekle

    • Çözüm Gezgini'da,

      • Eski bir .NET Framework Projesi (SDK olmayan stil) için birim testi projenizin Başvurular düğümünü genişletin.

      • .NET Framework, .NET Core veya .NET 5+ hedefleyen SDK stilinde bir proje için, Derlemeler, Projeler veya Paketler altında sahtesini yapmak istediğiniz derlemeyi bulmak için Bağımlılıklar düğümünü genişletin.

      • Visual Basic'te çalışıyorsanız Başvurular düğümünü görmek için Çözüm Gezgini araç çubuğunda Tüm Dosyaları Göster'i seçin.

    • tanımını System.IO.File.ReadAllLinesiçeren derlemeyi System seçin.

    • Kısayol menüsünde Sahte Derleme Ekle'yi seçin.

    Screnshot of the command Add Fakes Assembly.

Tüm türler dolgularla kullanılamadığından, derleme bazı uyarı ve hatalarla sonuçlandığından, bunları dışlamak için Fakes\mscorlib.fakes içeriğini değiştirmeniz gerekir.

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
  <Assembly Name="mscorlib" Version="4.0.0.0"/>
  <StubGeneration>
    <Clear/>
  </StubGeneration>
  <ShimGeneration>
    <Clear/>
    <Add FullName="System.IO.File"/>
    <Remove FullName="System.IO.FileStreamAsyncResult"/>
    <Remove FullName="System.IO.FileSystemEnumerableFactory"/>
    <Remove FullName="System.IO.FileInfoResultHandler"/>
    <Remove FullName="System.IO.FileSystemInfoResultHandler"/>
    <Remove FullName="System.IO.FileStream+FileStreamReadWriteTask"/>
    <Remove FullName="System.IO.FileSystemEnumerableIterator"/>
  </ShimGeneration>
</Fakes>

Birim testi oluşturma

  1. Aşağıdakini eklemek için varsayılan dosyayı UnitTest1.cs değiştirin TestMethod

    [TestMethod]
    public void TestFileReadAllLine()
    {
        using (ShimsContext.Create())
        {
            // Arrange
            System.IO.Fakes.ShimFile.ReadAllLinesString = (s) => new string[] { "Hello", "World", "Shims" };
    
            // Act
            var target = new HexFile("this_file_doesnt_exist.txt");
    
            Assert.AreEqual(3, target.Records.Length);
        }
    }
    

    Tüm dosyaları gösteren Çözüm Gezgini aşağıdadır

    Screenshot of Solution Explorer showing all files.

  2. Test Gezgini'ni açın ve testi çalıştırın.

Her dolgu bağlamı düzgün bir şekilde atılması kritik önem taşır. Bir kural olarak, kayıtlı dolguların düzgün temizlenmesini sağlamak için deyiminin using içini çağırınShimsContext.Create. Örneğin, yöntemini her zaman Ocak 2000'in ilkini DateTime.Now döndüren bir temsilciyle değiştiren bir test yöntemi için dolgu kaydedebilirsiniz. Test yönteminde kayıtlı dolguyu temizlemeyi unutursanız, test çalıştırmasının geri kalanı her zaman ilk Ocak 2000 DateTime.Now değerini döndürür. Bu şaşırtıcı ve kafa karıştırıcı olabilir.


Dolgu Sınıfları için Adlandırma Kuralları

Dolgu sınıf adları, özgün tür adına ön ek eklenerek Fakes.Shim oluşturulur. Parametre adları yöntem adına eklenir. (System.Fakes'e herhangi bir derleme başvurusu eklemeniz gerekmez.)

    System.IO.File.ReadAllLines(path);
    System.IO.Fakes.ShimFile.ReadAllLinesString = (path) => new string[] { "Hello", "World", "Shims" };

Dolguların Nasıl Çalıştığını Anlama

Dolgular, test edilen uygulamanın kod tabanına detours ekleyerek çalışır. Özgün yönteme bir çağrı olduğunda Fakes sistemi bu çağrıyı yeniden yönlendirmeye müdahale eder ve özel dolgu kodunuzun özgün yöntem yerine yürütülmesine neden olur.

Bu sapmaların çalışma zamanında dinamik olarak oluşturulduğunu ve kaldırıldığını unutmayın. Detours her zaman bir ShimsContextyaşam süresi içinde oluşturulmalıdır. ShimsContext atıldığında, içinde oluşturulan tüm etkin dolgular da kaldırılır. Bunu verimli bir şekilde yönetmek için, bir using deyim içinde detours oluşturulmasını kapsüllemenizi öneririz.


Farklı yöntemler için dolgular

Dolgular çeşitli yöntem türlerini destekler.

Statik yöntemler

Statik yöntemlerin dolgusu oluşturulurken dolguları tutan özellikler dolgu türü içinde barındırılır. Bu özellikler yalnızca hedeflenen yönteme temsilci eklemek için kullanılan bir ayarlayıcıya sahiptir. Örneğin, statik yöntemiyle MyMethodadlı MyClass bir sınıfımız varsa:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

Sürekli 5 döndürecek şekilde bir dolgu MyMethod ekleyebiliriz:

// unit test code
ShimMyClass.MyMethod = () => 5;

Örnek yöntemleri (tüm örnekler için)

Statik yöntemler gibi örnek yöntemleri de tüm örnekler için dolgulanabilir. Bu dolguları tutan özellikler, karışıklığı önlemek için AllInstances adlı iç içe yerleştirilmiş bir türe yerleştirilir. Örnek yöntemine MyMethodsahip bir sınıfımız MyClass varsa:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Örneğinden bağımsız olarak tutarlı olarak 5 döndürmesi için MyMethod öğesine dolgu ekleyebiliriz:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

oluşturulan tür yapısı ShimMyClass aşağıdaki gibi görünür:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

Bu senaryoda Fakes, çalışma zamanı örneğini temsilcinin ilk bağımsız değişkeni olarak geçirir.

Örnek yöntemleri (Tek Çalışma Zamanı Örneği)

Örnek yöntemleri, çağrının alıcıya bağlı olarak farklı temsilciler kullanılarak da kullanılabilir. Bu, aynı örnek yönteminin türün örneği başına farklı davranışlar sergilemesini sağlar. Bu dolguları tutan özellikler, dolgu türünün kendisinin örnek yöntemleridir. Örneklenen her dolgu türü, dolgulu bir türün ham örneğine bağlanır.

Örneğin, örnek yöntemine MyMethodsahip bir sınıf MyClass verilmiştir:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

İlkinin tutarlı olarak 5, ikincisinin tutarlı olarak 10 döndürmesi için MyMethod iki dolgu türü oluşturabiliriz:

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

oluşturulan tür yapısı ShimMyClass aşağıdaki gibi görünür:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

Gerçek dolgulu tür örneğine Instance özelliği aracılığıyla erişilebilir:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

Dolgu türü ayrıca dolgu türüne örtük bir dönüştürme de içerir ve dolgu türünü doğrudan kullanmanıza olanak sağlar:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime instance

Oluşturucular

Oluşturucular, dolgulama için özel durum değildir; bunlar da gelecekte oluşturulacak nesnelere dolgu türleri eklemek için dolgulanabilir. Örneğin, her oluşturucu dolgu türü içinde adlı Constructorstatik bir yöntem olarak temsil edilir. Şimdi tamsayı kabul eden bir oluşturucuya sahip bir sınıf MyClass düşünelim:

public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

Oluşturucu için bir dolgu türü, oluşturucuya geçirilen değerden bağımsız olarak, Değer alıcısı çağrıldığında gelecekteki her örnek -5 döndürecek şekilde ayarlanabilir:

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

Her dolgu türü iki tür oluşturucuyu kullanıma sunar. Yeni bir örnek gerektiğinde varsayılan oluşturucu kullanılmalıdır, ancak bağımsız değişken olarak bir dolgu örneği alan oluşturucu yalnızca oluşturucu dolgularında kullanılmalıdır:

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

için ShimMyClass oluşturulan türün yapısı aşağıdaki gibi gösterilebilir:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

Temel üyelere erişme

Temel üyelerin dolgu özelliklerine, temel tür için dolgu oluşturularak ve alt örnek temel dolgu sınıfının oluşturucusuna girilerek ulaşılabilir.

Örneğin, örnek yöntemi MyMethod ve alt türü MyChildolan bir sınıf MyBase düşünün:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

Dolgusu MyBase , yeni ShimMyBase bir dolgu başlatılarak ayarlanabilir:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

Temel dolgu oluşturucusunun parametresi olarak geçirildiğinde alt dolgu türünün örtük olarak alt örneğe dönüştürüldüğünü unutmayın.

ve için ShimMyChildShimMyBase oluşturulan türün yapısı aşağıdaki koda benzetilebilir:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

Statik oluşturucular

Dolgu türleri, bir türün statik oluşturucusunun dolgusu için statik bir yöntem StaticConstructor sunar. Statik oluşturucular yalnızca bir kez yürütüleceğinden, türün herhangi bir üyesine erişilmeden önce dolgunun yapılandırıldığından emin olmanız gerekir.

Sonlandırıcılar

Sonlandırıcılar Fakes'ta desteklenmez.

Özel yöntemler

Fakes kod oluşturucusu, imzada yalnızca görünür türler olan özel yöntemler için dolgu özellikleri oluşturur, yani parametre türleri ve dönüş türü görünür.

Bağlama arabirimleri

Dolgulu bir tür bir arabirim uyguladığında, kod oluşturucu bu arabirimdeki tüm üyeleri aynı anda bağlamasına olanak tanıyan bir yöntem yayar.

Örneğin, uygulayan IEnumerable<int>bir sınıf MyClass verilir:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

Bağlama yöntemini çağırarak MyClass içindeki uygulamalarını IEnumerable<int> dolgulayabilirsiniz:

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

oluşturulan tür yapısı ShimMyClass aşağıdaki koda benzer:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

Varsayılan Davranışı Değiştir

Oluşturulan her dolgu türü, özelliği aracılığıyla erişilebilen arabirimin bir örneğini IShimBehaviorShimBase<T>.InstanceBehavior içerir. Bu davranış, bir istemci açıkça dolgulanmamış bir örnek üyesini çağırdığı her durumda çağrılır.

Varsayılan olarak, belirli bir davranış ayarlanmamışsa statik ShimBehaviors.Current özellik tarafından döndürülen örneği kullanır ve bu genellikle bir NotImplementedException özel durum oluşturur.

Herhangi bir dolgu örneğinin özelliğini ayarlayarak InstanceBehavior bu davranışı istediğiniz zaman değiştirebilirsiniz. Örneğin, aşağıdaki kod parçacığı hiçbir şey yapmak veya dönüş türünün varsayılan değerini döndürmek için davranışı değiştirir; örneğin: default(T)

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimBehaviors.DefaultValue;

Statik özelliği ayarlayarakShimBehaviors.Current, tüm titrek örneklerin InstanceBehavior davranışını (özelliğin açıkça tanımlanmadığı durumlarda) genel olarak da değiştirebilirsiniz:

// unit test code
// change default shim for all shim instances where the behavior has not been set
ShimBehaviors.Current = ShimBehaviors.DefaultValue;

Dış Bağımlılıklarla Etkileşimleri Tanımlama

Kodunuzun dış sistemlerle veya bağımlılıklarla ne zaman etkileşimde olduğunu belirlemeye yardımcı olmak için (olarak environmentadlandırılır), dolguları kullanarak bir türün tüm üyelerine belirli bir davranış atayabilirsiniz. Bu statik yöntemleri içerir. Dolgu türünün statik Behavior özelliğinde davranışı ayarlayarakShimBehaviors.NotImplemented, bu türün açıkça dolgulanmamış bir üyesine yapılan tüm erişimler bir NotImplementedExceptionoluşturur. Bu, test sırasında kodunuzun dış sisteme veya bağımlılıklara erişmeye çalıştığına işaret eden yararlı bir sinyal görevi görebilir.

Birim test kodunuzda bunun nasıl ayarlanacağına yönelik bir örnek aşağıda verilmişti:

// unit test code
// Assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.Behavior = ShimBehaviors.NotImplemented;

Kolaylık sağlamak için, aynı etkiyi elde etmek için bir kısaltma yöntemi de sağlanır:

// Shorthand to assign the NotImplementedException behavior to ShimMyClass
ShimMyClass.BehaveAsNotImplemented();

Dolgu Metotları İçinden Özgün Yöntemleri Çağırma

Dolgu yönteminin yürütülmesi sırasında özgün yöntemi yürütmeniz gerekebilecek senaryolar olabilir. Örneğin, yönteme geçirilen dosya adını doğruladıktan sonra dosya sistemine metin yazmak isteyebilirsiniz.

Bu durumu işlemeye yönelik yaklaşımlardan biri, aşağıdaki kodda gösterildiği gibi bir temsilci ve ShimsContext.ExecuteWithoutShims()kullanarak özgün yönteme yapılan çağrıyı kapsüllemektir:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

Alternatif olarak dolguyu null yapabilir, özgün yöntemi çağırabilir ve dolguyu geri yükleyebilirsiniz.

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter");
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

Dolgu Türleriyle Eşzamanlılığı İşleme

Dolgu türleri AppDomain içindeki tüm iş parçacıklarında çalışır ve iş parçacığı benzitesine sahip değildir. Eşzamanlılığı destekleyen bir test çalıştırıcısı kullanmayı planlıyorsanız, bu özelliği aklınızda bulundurmanız çok önemlidir. Dolgu türlerini içeren testlerin eşzamanlı olarak çalıştırılamayacağına dikkat edin, ancak bu kısıtlama Fakes çalışma zamanı tarafından uygulanmaz.

Shimming System.Environment

Sınıfını dolgulu System.Environment hale getirmek istiyorsanız dosyada bazı değişiklikler mscorlib.fakes yapmanız gerekir. Assembly öğesinin ardından aşağıdaki içeriği ekleyin:

<ShimGeneration>
    <Add FullName="System.Environment"/>
</ShimGeneration>

Bu değişiklikleri yaptıktan ve çözümü yeniden derledikten sonra, sınıftaki System.Environment yöntemler ve özellikler artık kullanılabilir. Aşağıda yöntemine nasıl davranış atayabileceğinize ilişkin bir örnek verilmişti GetCommandLineArgsGet :

System.Fakes.ShimEnvironment.GetCommandLineArgsGet = ...

Bu değişiklikleri yaparak kodunuzun kapsamlı birim testi için temel bir araç olan sistem ortamı değişkenleriyle nasıl etkileşim kuracaklarını denetleme ve test etme olanağını ortaya çıkarmış olursunuz.