[थ्योरी] के लिए जटिल पैरामीटर पास करें


98

Xunit में एक अच्छी सुविधा है : आप एक Theoryविशेषता के साथ एक परीक्षण बना सकते हैं और InlineDataविशेषताओं में डेटा डाल सकते हैं , और xUnit कई परीक्षण उत्पन्न करेगा, और उन सभी का परीक्षण करेगा।

मैं कुछ इस तरह करना चाहते हैं, लेकिन मेरे विधि के लिए पैरामीटर नहीं 'सरल डेटा' (की तरह हैं string, int, double), लेकिन मेरी कक्षा की एक सूची:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }

3
यदि यह आपके वातावरण में समझ में आता है, तो आप इसे F # में बहुत कम शोर के साथ कर सकते हैं: - stackoverflow.com/a/35127997/11635
Ruben Bartelink

1
एक पूर्ण मार्गदर्शिका जो जटिल वस्तुओं को एक पैरामीटर के रूप में टेस्ट करने के लिए यूनिट टेस्ट में जटिल प्रकारों को
भेजती है

जवाबों:


137

xxxxDataXUnit में कई विशेषताएँ हैं । उदाहरण के लिए PropertyDataविशेषता देखें।

आप एक ऐसी संपत्ति लागू कर सकते हैं जो वापस आती है IEnumerable<object[]>। प्रत्येक object[]कि यह विधि उत्पन्न करता है तो आपकी [Theory]विधि के लिए एक कॉल के लिए एक पैरामीटर के रूप में "अनपैक्ड" होगा ।

एक अन्य विकल्प है ClassData, जो समान काम करता है, लेकिन विभिन्न वर्गों / नामस्थानों में परीक्षणों के बीच 'जनरेटर' को आसानी से साझा करने की अनुमति देता है, और वास्तविक परीक्षण विधियों से 'डेटा जनरेटर' को भी अलग करता है।

यहाँ से इन उदाहरणों को देखें :

प्रॉपर्टीडाटा उदाहरण

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}

ClassData उदाहरण

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}

@dcastro: हाँ, मैं वास्तव में कुछ मूल xunit डॉक्स पर खोज रहा हूँ
quetzalcoatl

2
@ निक: मैं सहमत हूं कि यह प्रॉपर्टीडाटा के समान है, लेकिन इसके अलावा, आपने इसका कारण बताया है static:। यही कारण है कि मैं नहीं होगा। क्लासडाटा वह है जब आप स्टैटिक्स से बचना चाहते हैं। ऐसा करके, आप पुन: उपयोग कर सकते हैं (यानी घोंसला) जनरेटर को आसान बना सकते हैं।
quetzalcoatl

1
किसी भी विचार ClassData के साथ क्या हुआ? मैं इसे xUnit2.0 में नहीं पा सकता हूं, अभी के लिए, मैं स्थिर विधि के साथ सदस्य डेटा का उपयोग कर रहा हूं, जो वर्ग का नया उदाहरण बनाता है, और उसे लौटाता है।
एर्टि-क्रिस इल्मा

14
@Erti, विशेषता [MemberData("{static member}", MemberType = typeof(MyClass))]को बदलने के लिए उपयोग करें ClassData
Junle Li

6
C # 6 के अनुसार यह nameofएक संपत्ति के नाम को हार्डकोड करने के बजाय कीवर्ड का उपयोग करने की सिफारिश करेगा (आसानी से लेकिन चुपचाप टूट जाता है)।
सारा

40

@ Quetzalcoatl के उत्तर को अपडेट करने के लिए: इस विशेषता [PropertyData]को उलट दिया गया है [MemberData]जिससे तर्क किसी भी स्थिर विधि, क्षेत्र, या संपत्ति का स्ट्रिंग नाम लेता है जो एक रिटर्न देता है IEnumerable<object[]>। (मुझे यह विशेष रूप से अच्छा लगता है कि एक इटेरेटर विधि है जो वास्तव में एक समय में परीक्षण के मामलों की गणना कर सकती है , उन्हें ऊपर उठाते हुए वे गणना कर रहे हैं।)

सीमांकक द्वारा लौटाए गए अनुक्रम में प्रत्येक तत्व एक है object[]और प्रत्येक सरणी की लंबाई समान होनी चाहिए और यह लंबाई आपके परीक्षण मामले के तर्कों की संख्या होनी चाहिए (विशेषता के साथ एनोटेट किया गया है [MemberData]और प्रत्येक तत्व का संगत विधि पैरामीटर के समान प्रकार होना चाहिए (या शायद वे परिवर्तनीय प्रकार हो सकते हैं, मुझे नहीं पता।)

( XUnit.net मार्च 2014 और उदाहरण कोड के साथ वास्तविक पैच के लिए रिलीज़ नोट देखें ।)


2
@davidbak कोडप्लेक्स चला गया है। लिंक काम नहीं कर रहा है
किशन वैष्णव

11

अनाम ऑब्जेक्ट सरणियों का निर्माण करना डेटा के निर्माण का सबसे आसान तरीका नहीं है इसलिए मैंने अपनी परियोजना में इस पैटर्न का उपयोग किया

पहले कुछ पुन: प्रयोज्य, साझा वर्गों को परिभाषित करें

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}

अब आपका व्यक्तिगत परीक्षण और सदस्य डेटा लिखना आसान है और क्लीनर ...

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, bool expectedResult, string testDescription)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}

Descriptionआपके कई परीक्षण मामलों में से एक में विफल होने पर स्ट्रिंग संपत्ति अपने आप को एक हड्डी फेंकने के लिए है


1
यह मुझे पंसद है; इसकी कुछ वास्तविक क्षमता है एक बहुत ही जटिल वस्तु के लिए मुझे 90+ गुणों पर मान्यताओं को मान्य करना होगा। मैं एक साधारण JSON ऑब्जेक्ट में पास कर सकता हूं, इसे डिसेरिज़लाइज़ कर सकता हूं, और एक परीक्षण पुनरावृत्ति के लिए डेटा उत्पन्न कर सकता हूं। बहुत बढ़िया।
गुस्टिन

1
IsValid Testmethod के लिए पैरामीटर मिश्रित नहीं हैं - क्या यह IsValid (आउटपुटेंट, एक्सप्रेक्टेडResult, testDescription) नहीं होना चाहिए?
पास्ताकूल

9

मान लीजिए कि हमारे पास एक जटिल कार क्लास है जिसमें निर्माता वर्ग है:

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

हम कार क्लास को थ्योरी टेस्ट में भरने और पास करने जा रहे हैं।

इसलिए 'CarClassData' वर्ग बनाएं, जो नीचे की तरह कार वर्ग का एक उदाहरण देता है:

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

यह एक परीक्षण विधि (कारटेस्ट) बनाने और कार को एक पैरामीटर के रूप में परिभाषित करने का समय है:

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}

सिद्धांत में जटिल प्रकार

शुभ लाभ


3
यह उत्तर स्पष्ट रूप से थ्योरी इनपुट के रूप में एक कस्टम प्रकार पास करने के प्रश्न को संबोधित करता है जो चयनित उत्तर से गायब प्रतीत होता है।
जेडी कैन

1
यह वास्तव में उपयोग-केस है जिसे मैं देख रहा था कि एक थ्योरी के पैरामीटर के रूप में एक जटिल प्रकार कैसे पारित किया जाए। अच्छी तरह से काम! यह वास्तव में MVP पैटर्न के परीक्षण के लिए भुगतान करता है। मैं अब सभी प्रकार के राज्यों में एक दृश्य के कई अलग-अलग उदाहरणों को सेट कर सकता हूं और उन सभी को एक ही थ्योरी में पास कर सकता हूं जो उस दृश्य पर प्रस्तुतकर्ता विधियों के प्रभावों का परीक्षण करता है। इसे प्यार करना!
डेनिस एम। रसोई

3

आप इस तरह से कोशिश कर सकते हैं:

public class TestClass {

    bool isSaturday(DateTime dt)
    {
       string day = dt.DayOfWeek.ToString();
       return (day == "Saturday");
    }

    [Theory]
    [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
    public void test(int i)
    {
       // parse test case
       var input = TestCase.IsSaturdayTestCase[i];
       DateTime dt = (DateTime)input[0];
       bool expected = (bool)input[1];

       // test
       bool result = isSaturday(dt);
       result.Should().Be(expected);
    }   
}

परीक्षण डेटा रखने के लिए एक और वर्ग बनाएँ:

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}

1

अपनी जरूरतों के लिए मैं सिर्फ कुछ परीक्षणों के माध्यम से 'परीक्षण उपयोगकर्ताओं' की एक श्रृंखला को चलाना चाहता था - लेकिन [क्लासडेटा] आदि को मेरी ज़रूरत के लिए ओवरकिल लग रहा था (क्योंकि वस्तुओं की सूची प्रत्येक परीक्षण के लिए स्थानीय थी)।

इसलिए मैंने निम्नलिखित किया, परीक्षण के अंदर एक सरणी के साथ - बाहर से अनुक्रमित:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}

टेस्ट की मंशा को स्पष्ट रखते हुए इसने मेरा लक्ष्य हासिल कर लिया। आपको बस अनुक्रमणिका को सिंक में रखने की आवश्यकता है लेकिन यह सब है।

परिणामों में अच्छा लग रहा है, यह ढहने योग्य है और यदि आप एक त्रुटि प्राप्त करते हैं तो आप एक विशिष्ट उदाहरण को फिर से जोड़ सकते हैं:

यहां छवि विवरण दर्ज करें


"परिणामों में अच्छा लग रहा है, यह ढहने योग्य है और यदि आप एक त्रुटि प्राप्त करते हैं तो आप एक विशिष्ट उदाहरण को फिर से जोड़ सकते हैं"। बहुत अच्छी बात है। एक बड़ा दोष यह MemberDataप्रतीत होता है कि आप एक विशिष्ट परीक्षण इनपुट के साथ परीक्षण को नहीं देख सकते हैं और न ही चला सकते हैं। यह बेकार है।
ओलिवर पीयरमैन जुएल

वास्तव में, मैंने अभी काम किया है कि यह संभव है MemberDataअगर आप उपयोग करते हैं TheoryDataऔर वैकल्पिक रूप से IXunitSerializable। अधिक जानकारी और यहाँ देखें ... github.com/xunit/xunit/issues/429#issuecomment-108187109
ओलिवर पियरमैन

1

इसी तरह से मैंने आपकी समस्या का समाधान किया, मेरा भी यही हाल था। तो कस्टम वस्तुओं और प्रत्येक रन पर वस्तुओं की एक अलग संख्या के साथ इनलाइन।

    [Theory]
    [ClassData(typeof(DeviceTelemetryTestData))]
    public async Task ProcessDeviceTelemetries_TypicalDeserialization_NoErrorAsync(params DeviceTelemetry[] expected)
    {
        // Arrange
        var timeStamp = DateTimeOffset.UtcNow;

        mockInflux.Setup(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>())).ReturnsAsync("Success");

        // Act
        var actual = await MessageProcessingTelemetry.ProcessTelemetry(JsonConvert.SerializeObject(expected), mockInflux.Object);

        // Assert
        mockInflux.Verify(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>()), Times.Once);
        Assert.Equal("Success", actual);
    }

तो यह मेरा यूनिट टेस्ट है, पैरास पैरामीटर पर ध्यान दें । यह किसी भिन्न ऑब्जेक्ट को भेजने की अनुमति देता है। और अब मेरा DeviceTelemetryTestData वर्ग:

    public class DeviceTelemetryTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

आशा करता हूँ की ये काम करेगा !


-1

मुझे लगता है कि आप यहाँ गलत है। Theoryवास्तव में xUnit विशेषता का क्या अर्थ है: आप इस फ़ंक्शन को उन विशेष / यादृच्छिक मानों को भेजकर परीक्षण करना चाहते हैं, जिन्हें यह फ़ंक्शन-अंडर-टेस्ट प्राप्त होता है। इसका मतलब है कि यह है कि आप इस तरह के रूप में अगले विशेषता, के रूप में क्या निर्धारित किए हैं: InlineData, PropertyData, ClassData, आदि .. उन मापदंडों के लिए स्रोत होगा। इसका मतलब है कि आपको उन मापदंडों को प्रदान करने के लिए स्रोत ऑब्जेक्ट का निर्माण करना चाहिए। आपके मामले में मुझे लगता है कि आपको ClassDataस्रोत के रूप में ऑब्जेक्ट का उपयोग करना चाहिए । इसके अलावा - कृपया ध्यान दें कि ClassDataइनसे विरासत में मिला:IEnumerable<> - इसका मतलब है कि हर बार IEnumerable<>मान उत्पन्न करने तक फ़ंक्शन-अंडर-टेस्ट के लिए आने वाले मापदंडों के रूप में उत्पन्न मापदंडों का एक और सेट उपयोग किया जाएगा ।

यहाँ उदाहरण: टॉम ड्यूपॉन्ट .NET

उदाहरण गलत हो सकता है - मैंने लंबे समय तक xUnit का उपयोग नहीं किया

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.