xUnit.net: Global setup + teardown?


108

คำถามนี้เป็นคำถามเกี่ยวกับกรอบการทดสอบหน่วยxUnit.net

ฉันจำเป็นต้องเรียกใช้โค้ดก่อนที่จะทำการทดสอบใด ๆ และยังมีโค้ดบางส่วนหลังจากการทดสอบทั้งหมดเสร็จสิ้น ฉันคิดว่าควรมีอินเทอร์เฟซแอตทริบิวต์หรือเครื่องหมายบางอย่างเพื่อระบุการเริ่มต้นส่วนกลางและรหัสการสิ้นสุด แต่หาไม่พบ

หรืออีกวิธีหนึ่งถ้าฉันเรียกใช้ xUnit โดยทางโปรแกรมฉันสามารถบรรลุสิ่งที่ฉันต้องการด้วยรหัสต่อไปนี้:

static void Main()
{
    try
    {
        MyGlobalSetup();
        RunAllTests();  // What goes into this method?
    }
    finally
    {
        MyGlobalTeardown();
    }
}

ใครช่วยให้คำแนะนำเกี่ยวกับวิธีการประกาศหรือเรียกใช้โค้ดการตั้งค่า / การฉีกขาดทั่วโลกโดยใช้โปรแกรมได้บ้าง


1
ฉันเดาว่านี่คือคำตอบ: stackoverflow.com/questions/12379949/…
the_joric

คำตอบ:


128

เท่าที่ฉันรู้ xUnit ไม่มีจุดขยายการเริ่มต้น / การฉีกขาดทั่วโลก อย่างไรก็ตามมันเป็นเรื่องง่ายที่จะสร้าง เพียงสร้างคลาสทดสอบพื้นฐานที่ดำเนินการIDisposableและเริ่มต้นในตัวสร้างและการฉีกขาดของคุณในIDisposable.Disposeวิธีการ สิ่งนี้จะมีลักษณะดังนี้:

public abstract class TestsBase : IDisposable
{
    protected TestsBase()
    {
        // Do "global" initialization here; Called before every test method.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Called after every test method.
    }
}

public class DummyTests : TestsBase
{
    // Add test methods
}

อย่างไรก็ตามการตั้งค่าคลาสพื้นฐานและรหัสการฉีกขาดจะดำเนินการสำหรับการโทรแต่ละครั้ง นี่อาจไม่ใช่สิ่งที่คุณต้องการเนื่องจากไม่มีประสิทธิภาพมากนัก เวอร์ชันที่ปรับให้เหมาะสมยิ่งขึ้นจะใช้IClassFixture<T>อินเทอร์เฟซเพื่อให้แน่ใจว่าฟังก์ชันการเริ่มต้น / การฉีกขาดทั่วโลกจะถูกเรียกเพียงครั้งเดียว สำหรับเวอร์ชันนี้คุณไม่ได้ขยายคลาสพื้นฐานจากคลาสทดสอบของคุณ แต่ใช้IClassFixture<T>อินเทอร์เฟซที่Tอ้างถึงคลาสฟิกซ์เจอร์ของคุณ:

using Xunit;

public class TestsFixture : IDisposable
{
    public TestsFixture ()
    {
        // Do "global" initialization here; Only called once.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Only called once.
    }
}

public class DummyTests : IClassFixture<TestsFixture>
{
    public DummyTests(TestsFixture data)
    {
    }
}

สิ่งนี้จะส่งผลให้ตัวสร้างTestsFixtureถูกเรียกใช้เพียงครั้งเดียวสำหรับทุกคลาสที่อยู่ระหว่างการทดสอบ จึงขึ้นอยู่กับว่าคุณต้องการเลือกอะไรระหว่างสองวิธี


4
ดูเหมือนว่าจะไม่มี IUseFixture อีกต่อไปโดยถูกแทนที่ด้วย IClassFixture
GaTechThomas

9
ในขณะที่ใช้งานได้ฉันคิดว่า CollectionFixture ในคำตอบจาก Geir Sagberg นั้นเหมาะสมกว่าสำหรับสถานการณ์นี้เนื่องจากได้รับการออกแบบมาเพื่อจุดประสงค์นี้โดยเฉพาะ คุณไม่จำเป็นต้องสืบทอดชั้นเรียนการทดสอบของคุณเพียงทำเครื่องหมายด้วย[Collection("<name>")]แอตทริบิวต์
MichelZ

10
มีวิธีใดบ้างในการตั้งค่า async และการแยกออก
Andrii

ดูเหมือนว่า MS ได้ใช้โซลูชัน IClassFixture เช่นกัน docs.microsoft.com/en-us/aspnet/core/test/...
lbrahim

3
XUnit มีตัวเลือกการเริ่มต้นสามแบบ: ต่อวิธีการทดสอบต่อคลาสทดสอบและครอบคลุมคลาสทดสอบหลายคลาส เอกสารอยู่ที่นี่: xunit.net/docs/shared-context
GHN

52

ฉันกำลังมองหาคำตอบเดียวกันและในเวลานี้เอกสาร xUnit มีประโยชน์มากในการใช้งาน Class Fixtures และ Collection Fixtures ซึ่งทำให้นักพัฒนามีฟังก์ชั่นการตั้งค่า / การแยกย่อยที่หลากหลายในระดับคลาสหรือกลุ่มของคลาส สิ่งนี้สอดคล้องกับคำตอบจาก Geir Sagberg และให้การใช้งานโครงกระดูกที่ดีเพื่อแสดงให้เห็นว่าควรมีลักษณะอย่างไร

https://xunit.github.io/docs/shared-context.html

ควรใช้คอลเลคชันเมื่อใด: เมื่อคุณต้องการสร้างบริบทการทดสอบเดียวและแบ่งปันระหว่างการทดสอบในชั้นเรียนการทดสอบต่างๆและล้างข้อมูลหลังจากการทดสอบทั้งหมดในชั้นเรียนทดสอบเสร็จสิ้น

บางครั้งคุณอาจต้องการแบ่งปันวัตถุติดตั้งระหว่างชั้นทดสอบหลายชั้น ตัวอย่างฐานข้อมูลที่ใช้สำหรับการติดตั้งคลาสเป็นตัวอย่างที่ดี: คุณอาจต้องการเริ่มต้นฐานข้อมูลด้วยชุดข้อมูลทดสอบจากนั้นปล่อยข้อมูลการทดสอบนั้นไว้เพื่อใช้โดยคลาสทดสอบหลายชั้น คุณสามารถใช้คุณลักษณะคอลเลกชันฟิกซ์เจอร์ของ xUnit.net เพื่อแชร์อินสแตนซ์อ็อบเจ็กต์เดียวระหว่างการทดสอบในคลาสทดสอบต่างๆ

ในการใช้อุปกรณ์ติดตั้งคอลเลกชันคุณต้องทำตามขั้นตอนต่อไปนี้:

สร้างคลาสฟิกซ์เจอร์และใส่โค้ดเริ่มต้นในตัวสร้างคลาสฟิกซ์เจอร์ หากคลาสฟิกซ์เจอร์จำเป็นต้องทำการล้างข้อมูลให้ใช้ IDisposable บนคลาสฟิกซ์เจอร์และใส่โค้ดล้างข้อมูลในเมธอด Dispose () สร้างคลาสนิยามคอลเลกชันตกแต่งด้วยแอตทริบิวต์ [CollectionDefinition] ตั้งชื่อเฉพาะที่จะระบุคอลเล็กชันการทดสอบ เพิ่ม ICollectionFixture <> ในคลาสนิยามคอลเลกชัน เพิ่มแอตทริบิวต์ [Collection] ให้กับคลาสทดสอบทั้งหมดที่จะเป็นส่วนหนึ่งของคอลเล็กชันโดยใช้ชื่อเฉพาะที่คุณระบุให้กับแอตทริบิวต์ [CollectionDefinition] ของคลาสการทดสอบ หากคลาสทดสอบต้องการเข้าถึงอินสแตนซ์ฟิกซ์เจอร์ให้เพิ่มเป็นอาร์กิวเมนต์ตัวสร้างและจะได้รับการจัดเตรียมโดยอัตโนมัติ นี่คือตัวอย่างง่ายๆ:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit.net ถือว่าอุปกรณ์ติดตั้งคอลเลกชันในลักษณะเดียวกับคลาสฟิกซ์เจอร์ยกเว้นอายุการใช้งานของอ็อบเจ็กต์ฟิกซ์เจอร์คอลเลกชันนานกว่า: ถูกสร้างขึ้นก่อนที่จะรันการทดสอบในคลาสทดสอบใด ๆ ในคอลเล็กชันและจะไม่มีการล้าง จนกว่าคลาสทดสอบทั้งหมดในคอลเล็กชันจะทำงานเสร็จสิ้น

นอกจากนี้ยังสามารถตกแต่งคอลเลคชันการทดสอบด้วย IClassFixture <> xUnit.net ถือว่าสิ่งนี้เหมือนกับว่าแต่ละคลาสทดสอบในคอลเลคชันการทดสอบได้รับการตกแต่งด้วยอุปกรณ์ประจำคลาส

คอลเล็กชันการทดสอบยังมีผลต่อวิธีที่ xUnit.net เรียกใช้การทดสอบเมื่อรันแบบขนาน สำหรับข้อมูลเพิ่มเติมโปรดดูที่การรันการทดสอบแบบขนาน

หมายเหตุสำคัญ: ส่วนควบต้องอยู่ในชุดประกอบเดียวกับการทดสอบที่ใช้


1
"คอลเลกชันการทดสอบสามารถตกแต่งด้วย IClassFixture <> ได้ xUnit.net ถือว่าสิ่งนี้เหมือนกับว่าคลาสทดสอบแต่ละคลาสในคอลเลคชันทดสอบได้รับการตกแต่งด้วยอุปกรณ์ประจำคลาส" ฉันมีโอกาสที่จะได้รับตัวอย่างเช่นนั้นหรือไม่? ฉันไม่ค่อยเข้าใจมัน
rtf

@TannerFaulkner การติดตั้งคลาสเป็นวิธีการตั้งค่าระดับ CLASS และการแยกย่อยเช่นเดียวกับที่คุณได้รับจากโครงการทดสอบ. net แบบเดิมเมื่อคุณมีวิธี Test Initialize: [TestInitialize] โมฆะสาธารณะ Initialize () {
Larry Smith

ปัญหาเดียวที่ฉันพบคือคุณต้องตกแต่งชั้นเรียนทดสอบของคุณด้วยCollectionแอตทริบิวต์เพื่อให้การตั้งค่า "ทั่วโลก" เกิดขึ้น นั่นหมายความว่าหากคุณมีสิ่งที่ต้องการตั้งค่าก่อนที่จะรันการทดสอบใด ๆ คุณจะต้องตกแต่งคลาสทดสอบทั้งหมดด้วยแอตทริบิวต์นี้ ในความคิดของฉันเปราะบางเกินไปเนื่องจากการลืมตกแต่งคลาสทดสอบเดียวอาจทำให้เกิดข้อผิดพลาดที่ยากต่อการติดตาม คงจะดีไม่น้อยหาก xUnit สร้างวิธีในการตั้งค่าและการแยกย่อยระดับโลกอย่างแท้จริง
Zodman

13

มีวิธีง่ายๆง่ายๆ ใช้ปลั๊กอิน Fody.ModuleInit

https://github.com/Fody/ModuleInit

เป็นแพ็กเกจ nuget และเมื่อคุณติดตั้งจะเพิ่มไฟล์ใหม่ชื่อ ModuleInitializer.csไปยังโปรเจ็กต์ มีวิธีการแบบคงที่ในที่นี้ซึ่งได้รับการสานเข้าไปในแอสเซมบลีหลังจากสร้างและรันทันทีที่โหลดแอสเซมบลีและก่อนที่จะรันอะไร

ฉันใช้สิ่งนี้เพื่อปลดล็อกสิทธิ์การใช้งานซอฟต์แวร์ไปยังไลบรารีที่ฉันซื้อ ฉันมักจะลืมที่จะปลดล็อกใบอนุญาตในการทดสอบแต่ละครั้งและลืมที่จะได้รับการทดสอบจากคลาสพื้นฐานซึ่งจะปลดล็อก ประกายไฟสว่างไสวที่เขียนไลบรารีนี้แทนที่จะบอกคุณว่าใบอนุญาตถูกล็อกนำเสนอข้อผิดพลาดเชิงตัวเลขเล็กน้อยซึ่งทำให้การทดสอบล้มเหลวหรือผ่านในเวลาที่ไม่ควร คุณจะไม่มีทางรู้เลยว่าคุณได้ปลดล็อกห้องสมุดอย่างถูกต้องหรือไม่ ตอนนี้โมดูลเริ่มต้นของฉันดูเหมือน

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
    /// <summary>
    /// Initializes the module.
    /// </summary>
    public static void Initialize()
    {
            SomeLibrary.LicenceUtility.Unlock("XXXX-XXXX-XXXX-XXXX-XXXX");
    }
}

และการทดสอบทั้งหมดที่อยู่ในชุดประกอบนี้จะมีการปลดล็อกใบอนุญาตอย่างถูกต้องสำหรับพวกเขา


2
ความคิดที่มั่นคง น่าเสียดายที่ดูเหมือนว่ายังใช้การทดสอบหน่วย DNX ไม่ได้
Jeff Dunlop

นี่เป็นความคิดที่แย่มากมันจะรันโค้ดเมื่อโหลดโมดูลไม่ใช่ก่อน tetsts ทั้งหมด
เงา

12

การติดตั้งส่วนแบ่งการ / TearDown รหัสระหว่างหลายชั้นเรียนคุณสามารถใช้ xUnit ของCollectionFixture

อ้าง:

ในการใช้อุปกรณ์ติดตั้งคอลเลกชันคุณต้องทำตามขั้นตอนต่อไปนี้:

  • สร้างคลาสฟิกซ์เจอร์และใส่โค้ดเริ่มต้นในตัวสร้างคลาสฟิกซ์เจอร์
  • หากคลาสฟิกซ์เจอร์จำเป็นต้องทำการล้างข้อมูลให้ใช้ IDisposable บนคลาสฟิกซ์เจอร์และใส่โค้ดล้างข้อมูลในเมธอด Dispose ()
  • สร้างคลาสนิยามคอลเลกชันตกแต่งด้วยแอตทริบิวต์ [CollectionDefinition] ตั้งชื่อเฉพาะที่จะระบุคอลเล็กชันการทดสอบ
  • เพิ่ม ICollectionFixture <> ในคลาสนิยามคอลเลกชัน
  • เพิ่มแอตทริบิวต์ [Collection] ให้กับคลาสทดสอบทั้งหมดที่จะเป็นส่วนหนึ่งของคอลเล็กชันโดยใช้ชื่อเฉพาะที่คุณระบุให้กับแอตทริบิวต์ [CollectionDefinition] ของคลาสการทดสอบ
  • หากคลาสทดสอบต้องการเข้าถึงอินสแตนซ์ฟิกซ์เจอร์ให้เพิ่มเป็นอาร์กิวเมนต์ตัวสร้างและจะได้รับการจัดเตรียมโดยอัตโนมัติ

0

หากคุณมีฟังก์ชันการเริ่มต้นส่วนกลางและฟังก์ชันล้างข้อมูลส่วนกลางคุณสามารถเขียนคลาสได้ดังนี้

[CollectionDefinition("TestEngine")]
public class TestEngineInitializer: IDisposable, ICollectionFixture<TestEngineInitializer>
{
    public TestEngineInitializer()
    {
        MyOwnTestEngine.Init();
    }

    public void Dispose()
    {
        MyOwnTestEngine.Cleanup();
    }
}

และสำหรับแต่ละคลาสทดสอบที่จำเป็นต้องดำเนินการเริ่มต้นนี้คุณต้องเพิ่มแอตทริบิวต์เพิ่มเติม:

[Collection("TestEngine")]
public class MyTests
{

สำคัญ: ชื่อที่ใช้Collectionและ - CollectionDefinitionแอตทริบิวต์ต้องตรงกัน

คุณสามารถใช้จัดเตรียมอินสแตนซ์คลาส TestEngine ลงในตัวสร้างได้เช่นกัน:

[Collection("TestEngine")]
public class MyTests
{
     public MyTests(TestEngineInitializer initializer)
     {
     }

แต่ไม่ได้บังคับ

เอกสารทั้งหมดเกี่ยวกับปัญหาอยู่ที่นี่:

https://xunit.net/docs/shared-context

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.