ดำเนินการทดสอบหน่วยตามลำดับ (แทนที่จะทำแบบขนาน)


115

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

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

ฉันใช้ xUnit.NET โดยหวังว่าเนื่องจากความสามารถในการขยายตัวของมันฉันจึงสามารถหาวิธีบังคับให้รันการทดสอบแบบอนุกรมได้ อย่างไรก็ตามฉันไม่มีโชคเลย ฉันหวังว่าใครบางคนที่นี่ใน SO พบปัญหาที่คล้ายกันและรู้วิธีรับการทดสอบหน่วยเพื่อเรียกใช้แบบอนุกรม

หมายเหตุ: ServiceHostเป็นคลาส WCF ซึ่งเขียนโดย Microsoft ฉันไม่มีความสามารถที่จะเปลี่ยนพฤติกรรมได้ การโฮสต์แต่ละจุดสิ้นสุดของบริการเพียงครั้งเดียวก็เป็นพฤติกรรมที่เหมาะสมเช่นกัน ... อย่างไรก็ตามไม่ได้เอื้อต่อการทดสอบหน่วยโดยเฉพาะ


พฤติกรรมเฉพาะของ ServiceHost นี้จะเป็นสิ่งที่คุณอาจต้องการกล่าวถึงหรือไม่?
Robert Harvey

ServiceHost เขียนโดย Microsoft ฉันไม่สามารถควบคุมมันได้ และในทางเทคนิคมันเป็นพฤติกรรมที่ถูกต้อง ... คุณไม่ควรมี ServiceHost มากกว่าหนึ่งรายการต่อจุดสิ้นสุด
jrista

1
ฉันมีปัญหาคล้ายกันในการพยายามเรียกใช้หลายตัวTestServerในนักเทียบท่า ดังนั้นฉันจึงต้องจัดลำดับการทดสอบการรวม
h-rai

คำตอบ:


138

การทดสอบแต่ละคลาสเป็นคอลเล็กชันการทดสอบที่ไม่ซ้ำกันและการทดสอบจะทำงานตามลำดับดังนั้นหากคุณใส่การทดสอบทั้งหมดในคอลเลคชันเดียวกันการทดสอบจะทำงานตามลำดับ

ใน xUnit คุณสามารถทำการเปลี่ยนแปลงต่อไปนี้เพื่อให้บรรลุสิ่งนี้:

ต่อไปนี้จะทำงานคู่ขนาน:

namespace IntegrationTests
{
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

ในการทำให้เป็นลำดับคุณต้องใส่ทั้งสองคลาสทดสอบภายใต้คอลเลคชันเดียวกัน:

namespace IntegrationTests
{
    [Collection("Sequential")]
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    [Collection("Sequential")]
    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

สำหรับข้อมูลเพิ่มเติมคุณสามารถอ้างอิงได้จากลิงค์นี้


29
ฉันคิดว่าคำตอบที่ไม่น่าพอใจ ดูเหมือนว่าจะใช้งานได้และฉันชอบความละเอียดเนื่องจากฉันมีการทดสอบแบบขนานและไม่ขนานกันในชุดประกอบเดียว
Igand

1
นี่เป็นวิธีที่ถูกต้องในการดำเนินการนี้โดยอ้างอิงเอกสาร Xunit
Håkon K. Olafsen

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

2
สิ่งนี้ใช้ไม่ได้กับฉันในโครงการ. net core ที่ฉันทำการทดสอบการรวมกับฐานข้อมูล sqlite การทดสอบยังคงดำเนินการควบคู่กันไป คำตอบที่ยอมรับได้ผล
user1796440

ขอบคุณมากสำหรับคำตอบนี้! จำเป็นต้องทำเช่นนี้เนื่องจากฉันมีการทดสอบการยอมรับในคลาสต่างๆที่ทั้งสองสืบทอดมาจาก TestBase เดียวกันและการทำงานพร้อมกันไม่ได้ดีกับ EF Core
kyanite

111

สำคัญ: คำตอบนี้ใช้กับ. NET Framework สำหรับหลัก dotnet ดูคำตอบ Dimitry xunit.runner.jsonเกี่ยวกับ

การทดสอบหน่วยที่ดีทั้งหมดควรแยกได้ 100% การใช้สถานะร่วมกัน (เช่นขึ้นอยู่กับstaticคุณสมบัติที่แก้ไขโดยการทดสอบแต่ละครั้ง) ถือเป็นการปฏิบัติที่ไม่ดี

ต้องบอกว่าคำถามของคุณเกี่ยวกับการเรียกใช้การทดสอบ xUnit ตามลำดับมีคำตอบ! ฉันพบปัญหาเดียวกันทุกประการเนื่องจากระบบของฉันใช้ตัวระบุตำแหน่งบริการแบบคงที่ (ซึ่งน้อยกว่าที่เหมาะสม)

โดยค่าเริ่มต้น xUnit 2.x จะรันการทดสอบทั้งหมดพร้อมกัน สิ่งนี้สามารถแก้ไขได้ต่อแอสเซมบลีโดยกำหนดCollectionBehaviorใน AssemblyInfo.cs ของคุณในโครงการทดสอบของคุณ

สำหรับการใช้งานแยกแต่ละชุด:

using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

หรือไม่มีการขนานเลยให้ใช้:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

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


5
สำหรับฉันมีทรัพยากรที่ใช้ร่วมกันระหว่างเมธอดในแต่ละคลาส การเรียกใช้การทดสอบจากชั้นเรียนหนึ่งจากนั้นอีกชั้นหนึ่งจะทำลายการทดสอบจากทั้งสองชั้น ฉันสามารถแก้ไขได้โดยใช้[assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]ไฟล์. ขอบคุณคุณ @Squiggle ฉันสามารถทำการทดสอบทั้งหมดและไปดื่มกาแฟได้! :)
Alielson Piffer

2
คำตอบจาก Abhinav Saxena นั้นละเอียดกว่าสำหรับ. NET Core
Yennefer

78

สำหรับโครงการ. NET Core ให้สร้างxunit.runner.jsonด้วย:

{
  "parallelizeAssembly": false,
  "parallelizeTestCollections": false
}

นอกจากนี้คุณcsprojควรมี

<ItemGroup>
  <None Update="xunit.runner.json"> 
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

สำหรับโครงการ. Net Core แบบเก่าคุณproject.jsonควรมีไฟล์

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}

2
ฉันถือว่า csproj dotnet core เทียบเท่าล่าสุดจะ<ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>เหมือนหรือคล้ายกัน?
Squiggle

3
สิ่งนี้ใช้ได้ผลกับฉันใน csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd

การปิดใช้งานการขนานกับ xUnit Theories หรือไม่
John Zabroski

นี่เป็นสิ่งเดียวที่ใช้ได้ผลสำหรับฉันฉันพยายามวิ่งเหมือนdotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falseแต่มันไม่ได้ผลสำหรับฉัน
harvzor

19

สำหรับโครงการ .NET หลักคุณสามารถกำหนดค่า xUnit กับxunit.runner.jsonไฟล์เป็นเอกสารที่https://xunit.github.io/docs/configuring-with-json.html

การตั้งค่าที่คุณต้องเปลี่ยนเพื่อหยุดการดำเนินการทดสอบแบบขนานคือparallelizeTestCollectionsค่าเริ่มต้นเป็นtrue:

ตั้งค่านี้เป็นtrueหากแอสเซมบลีเต็มใจที่จะทำการทดสอบภายในชุดประกอบนี้โดยขนานกัน ... ตั้งค่านี้เพื่อfalseปิดใช้งานการขนานทั้งหมดภายในชุดทดสอบนี้

ประเภทสคีมา JSON: บูลีน
ค่าดีฟอลต์:true

ดังนั้นน้อยที่สุดxunit.runner.jsonสำหรับจุดประสงค์นี้ดูเหมือน

{
    "parallelizeTestCollections": false
}

ตามที่ระบุไว้ในเอกสารอย่าลืมรวมไฟล์นี้ไว้ในบิลด์ของคุณโดย:

  • การตั้งค่าCopy to Output Directoryเพื่อคัดลอกถ้าใหม่กว่าในPropertiesของไฟล์ใน Visual Studio หรือ
  • การเพิ่ม

    <Content Include=".\xunit.runner.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    

    ไปยัง.csprojไฟล์ของคุณหรือ

  • การเพิ่ม

    "buildOptions": {
      "copyToOutput": {
        "include": [ "xunit.runner.json" ]
      }
    }
    

    ไปยังproject.jsonไฟล์ของคุณ

ขึ้นอยู่กับประเภทโครงการของคุณ

สุดท้ายนอกเหนือจากข้างต้นหากคุณใช้ Visual Studio ตรวจสอบให้แน่ใจว่าคุณไม่ได้คลิกปุ่มRun Tests In Parallelโดยไม่ได้ตั้งใจซึ่งจะทำให้การทดสอบทำงานแบบขนานแม้ว่าคุณจะปิดการใช้งานแบบขนานไปxunit.runner.jsonแล้วก็ตาม นักออกแบบ UI ของ Microsoft ได้สร้างปุ่มนี้ขึ้นมาอย่างมีเล่ห์เหลี่ยมซึ่งยากที่จะสังเกตเห็นและอยู่ห่างจากปุ่ม"เรียกใช้ทั้งหมด"ใน Test Explorer ประมาณหนึ่งเซนติเมตรเพียงเพื่อเพิ่มโอกาสที่คุณจะกดปุ่มโดยไม่ได้ตั้งใจ ล้มเหลวกะทันหัน:

ภาพหน้าจอที่มีปุ่มเป็นวงกลม


@JohnZabroski ฉันไม่เข้าใจคุณแก้ไขปัญหา ReSharper เกี่ยวข้องกับอะไรบ้าง? ฉันคิดว่าฉันคงติดตั้งแล้วเมื่อฉันเขียนคำตอบข้างต้น แต่ทุกอย่างที่นี่ไม่ขึ้นอยู่กับว่าคุณกำลังใช้อยู่หรือไม่? อะไรหน้าคุณเชื่อมโยงไปในการแก้ไขจะต้องทำด้วยการระบุxunit.runner.jsonไฟล์? และการระบุว่าxunit.runner.jsonต้องทำอย่างไรกับการทดสอบแบบรันซีรีส์?
Mark Amery

ฉันกำลังพยายามให้การทดสอบทำงานแบบอนุกรมและในตอนแรกคิดว่าปัญหาเกี่ยวข้องกับ ReSharper (เนื่องจาก ReSharper ไม่มีปุ่ม "Run Tests in Parallel" เหมือนกับ Visual Studio Test Explorer) อย่างไรก็ตามดูเหมือนว่าเมื่อฉันใช้ [ทฤษฎี] การทดสอบของฉันจะไม่แยกออกจากกัน นี่เป็นเรื่องแปลกเพราะทุกสิ่งที่ฉันอ่านชี้ให้เห็นว่า Class เป็นหน่วยที่ขนานกันได้น้อยที่สุด
John Zabroski

11

นี่เป็นคำถามเก่า แต่ฉันต้องการเขียนวิธีแก้ปัญหาให้กับผู้ที่เพิ่งค้นหาเช่นฉัน :)

หมายเหตุ:ฉันใช้วิธีนี้ในการทดสอบการรวม Dot Net Core WebUI กับ xunit เวอร์ชัน 2.4.1

สร้างคลาสว่างชื่อ NonParallelCollectionDefinitionClass จากนั้นให้แอตทริบิวต์ CollectionDefinition กับคลาสนี้ตามด้านล่าง (ส่วนที่สำคัญคือการตั้งค่า DisableParallelization = true)

using Xunit;

namespace WebUI.IntegrationTests.Common
{
    [CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
    public class NonParallelCollectionDefinitionClass
    {
    }
}

หลังจากนั้นให้เพิ่มแอตทริบิวต์คอลเลคชันลงในคลาสที่คุณไม่ต้องการให้ทำงานคู่ขนานดังต่อไปนี้ (ส่วนที่สำคัญคือชื่อคอลเลกชั่นต้องเหมือนกันกับชื่อที่ใช้ใน CollectionDefinition)

namespace WebUI.IntegrationTests.Controllers.Users
{
    [Collection("Non-Parallel Collection")]
    public class ChangePassword : IClassFixture<CustomWebApplicationFactory<Startup>>
    ...

เมื่อเราทำเช่นนี้ขั้นแรกจะทำการทดสอบคู่ขนานอื่น ๆ หลังจากนั้นการทดสอบอื่น ๆ ที่มีการเรียกใช้แอตทริบิวต์ Collection ("Non-Parallel Collection")


6

คุณสามารถใช้เพลย์ลิสต์

คลิกขวาที่วิธีทดสอบ -> เพิ่มลงในเพลย์ลิสต์ -> เพลย์ลิสต์ใหม่

จากนั้นคุณสามารถระบุลำดับการดำเนินการได้ค่าเริ่มต้นคือเมื่อคุณเพิ่มลงในรายการเล่น แต่คุณสามารถเปลี่ยนไฟล์เพลย์ลิสต์ได้ตามที่คุณต้องการ

ป้อนคำอธิบายภาพที่นี่


5

ผมไม่ทราบรายละเอียด แต่ดูเหมือนคุณอาจจะพยายามที่จะทำการทดสอบการรวมมากกว่าการทดสอบหน่วย หากคุณสามารถแยกการพึ่งพาได้ServiceHostนั่นอาจทำให้การทดสอบของคุณง่ายขึ้น (และเร็วขึ้น) ดังนั้น (เช่น) คุณอาจทดสอบสิ่งต่อไปนี้โดยอิสระ:

  • คลาสการอ่านการกำหนดค่า
  • โรงงาน ServiceHost (อาจเป็นการทดสอบการรวม)
  • คลาสเครื่องยนต์ที่ใช้IServiceHostFactoryและIConfiguration

เครื่องมือที่จะช่วยรวมเฟรมเวิร์กการแยก (จำลอง) และเฟรมเวิร์กคอนเทนเนอร์ IoC (เป็นทางเลือก) ดู:


ฉันไม่ได้พยายามทำการทดสอบการรวมระบบ ฉันจำเป็นต้องทำการทดสอบหน่วย ฉันมีความเชี่ยวชาญในข้อกำหนดและแนวปฏิบัติของ TDD / BDD อย่างถี่ถ้วน (IoC, DI, Mocking และอื่น ๆ ) ดังนั้นการดำเนินการของสิ่งต่างๆเช่นการสร้างโรงงานและการใช้อินเทอร์เฟซจึงไม่ใช่สิ่งที่ฉันต้องการ (ทำไปแล้ว ยกเว้นในกรณีของ ServiceHost เอง) ServiceHost ไม่ใช่การอ้างอิงที่สามารถแยกได้เนื่องจากไม่สามารถจำลองได้อย่างเหมาะสม (เนมสเปซของระบบ. NET) ฉันต้องการวิธีเรียกใช้การทดสอบหน่วยแบบเป็นชุด
jrista

1
@jrista - ไม่มีความตั้งใจในทักษะของคุณเลย ฉันไม่ใช่นักพัฒนา WCF แต่เป็นไปได้ไหมที่เอ็นจิ้นจะส่งคืนกระดาษห่อหุ้มรอบ ๆ ServiceHost ด้วยอินเทอร์เฟซบนกระดาษห่อหุ้ม หรืออาจเป็นโรงงานที่กำหนดเองสำหรับ ServiceHosts?
TrueWill

เอ็นจินการโฮสต์ไม่ส่งคืน ServiceHosts ใด ๆ มันไม่ได้ส่งคืนอะไรเลยเพียงแค่จัดการการสร้างการเปิดและการปิด ServiceHosts ภายใน ฉันสามารถสรุปประเภท WCF พื้นฐานทั้งหมดได้ แต่นั่นเป็นงานจำนวนมากที่ฉันไม่ได้รับอนุญาตให้ทำจริงๆ ตามที่ปรากฎปัญหาไม่ได้เกิดจากการดำเนินการแบบขนานและจะยังคงเกิดขึ้นระหว่างการทำงานปกติ ฉันเริ่มคำถามอื่นที่ SO เกี่ยวกับปัญหานี้และหวังว่าฉันจะได้รับคำตอบ
jrista

@TrueWill: BTW ฉันไม่ได้กังวลว่าคุณจะฝึกฝนทักษะของฉันเลย ... ฉันแค่ไม่ต้องการรับคำตอบมากมายที่ครอบคลุมเนื้อหาทั่วไปทั้งหมดเกี่ยวกับการทดสอบหน่วย ฉันต้องการคำตอบด่วนสำหรับปัญหาที่เฉพาะเจาะจง ขอโทษนะถ้าฉันพูดออกไปหน่อยไม่ได้ตั้งใจ ฉันมีเวลาค่อนข้าง จำกัด เพื่อให้สิ่งนี้ทำงานได้
jrista

3

บางทีคุณอาจใช้การทดสอบหน่วยขั้นสูงก็ได้ จะช่วยให้คุณกำหนดลำดับที่คุณใช้ทดสอบ ดังนั้นคุณอาจต้องสร้างไฟล์ cs ใหม่เพื่อโฮสต์การทดสอบเหล่านั้น

นี่คือวิธีที่คุณสามารถดัดวิธีทดสอบให้ทำงานตามลำดับที่คุณต้องการ

[Test]
[Sequence(16)]
[Requires("POConstructor")]
[Requires("WorkOrderConstructor")]
public void ClosePO()
{
  po.Close();

  // one charge slip should be added to both work orders

  Assertion.Assert(wo1.ChargeSlipCount==1,
    "First work order: ChargeSlipCount not 1.");
  Assertion.Assert(wo2.ChargeSlipCount==1,
    "Second work order: ChargeSlipCount not 1.");
  ...
}

โปรดแจ้งให้เราทราบว่าได้ผลหรือไม่


บทความที่ดี จริงๆแล้วฉันได้บุ๊คมาร์คไว้ที่ CP ขอบคุณสำหรับลิงก์ แต่เมื่อปรากฎปัญหาดูเหมือนจะลึกกว่ามากเนื่องจากนักวิ่งทดสอบดูเหมือนจะไม่ทำการทดสอบควบคู่กันไป
jrista

2
เดี๋ยวก่อนก่อนอื่นคุณบอกว่าคุณไม่ต้องการให้การทดสอบทำงานแบบขนานแล้วคุณบอกว่าปัญหาคือนักวิ่งทดสอบไม่ได้ทำการทดสอบพร้อมกัน ... แล้วไหนล่ะ
Graviton

ลิงก์ที่คุณให้ไว้ใช้งานไม่ได้อีกต่อไป และนี่คือสิ่งที่คุณสามารถทำได้กับ xunit?
Allen Wang

1

สำหรับฉันในแอปพลิเคชัน. Net Core Console เมื่อฉันต้องการเรียกใช้วิธีการทดสอบ (ไม่ใช่คลาส) แบบซิงโครนัสวิธีแก้ปัญหาเดียวที่ใช้ได้ผลตามที่อธิบายไว้ในบล็อกนี้: xUnit: Control the Test Execution Order


0

ฉันได้เพิ่มแอตทริบิวต์[Collection ("Sequential")]ในคลาสพื้นฐาน:

    namespace IntegrationTests
    {
      [Collection("Sequential")]
      public class SequentialTest : IDisposable
      ...


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

0

จนถึงตอนนี้คำตอบที่แนะนำยังไม่ได้ผลสำหรับฉัน ฉันมีแอพ dotnet core ที่มี XUnit 2.4.1 ฉันทำพฤติกรรมที่ต้องการได้สำเร็จด้วยวิธีแก้ปัญหาโดยใส่ตัวล็อกในการทดสอบแต่ละหน่วยแทน ในกรณีของฉันฉันไม่สนใจเกี่ยวกับการเรียกใช้คำสั่งเพียง แต่การทดสอบนั้นเป็นไปตามลำดับ

public class TestClass
{
    [Fact]
    void Test1()
    {
        lock (this)
        {
            //Test Code
        }
    }

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.