วิธีการจำลอง ConfigurationManager.AppSettings ด้วย moq


124

ฉันติดอยู่ที่จุดนี้ของรหัสที่ฉันไม่รู้ว่าจะล้อเลียนอย่างไร:

ConfigurationManager.AppSettings["User"];

ฉันต้องเยาะเย้ย ConfigurationManager แต่ฉันไม่ได้มีเงื่อนงำผมใช้ขั้นต่ำ

มีใครให้คำแนะนำได้บ้าง ขอบคุณ!

คำตอบ:


105

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

ดังนั้นคุณจะห่อ ConfigurationManager สิ่งที่ต้องการ:

public class Configuration: IConfiguration
{
    public User
    {
        get
        { 
            return ConfigurationManager.AppSettings["User"];
        }
    }
}

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


6
นี่เป็นแนวคิดในสิ่งที่ฉันกำลังทำเช่นกัน อย่างไรก็ตามฉันใช้ Castle DictionaryAdapter (ส่วนหนึ่งของCastle Core) ซึ่งสร้างการใช้งานอินเทอร์เฟซได้ทันที ฉันเคยเขียนเกี่ยวกับเรื่องนี้มาแล้ว: blog.andreloker.de/post/2008/09/05/… (เลื่อนลงไปที่ "A Solution" เพื่อดูว่าฉันใช้ Castle DictionaryAdapter อย่างไร)
Andre Loker

นั่นเป็นสิ่งที่ดีและเป็นบทความที่ดี ฉันจะต้องจำสิ่งนี้ไว้ในอนาคต
Joshua Enfield

ฉันอาจเพิ่ม - ขึ้นอยู่กับความบริสุทธิ์และการตีความของคุณซึ่งอาจเรียกว่า Delegate Proxy หรือ Adapter แทนก็ได้
Joshua Enfield

3
จากด้านบนก็แค่ใช้ Moq เป็น "ปกติ" ยังไม่ทดสอบ แต่มีบางอย่างเช่น: var configurationMock = new Mock<IConfiguration>();และสำหรับการตั้งค่า:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield

สถานการณ์นี้ใช้เมื่อเลเยอร์ขึ้นอยู่กับ IConfiguration และคุณจำเป็นต้องจำลอง IConfiguration แต่คุณจะทดสอบ IConfiguration Implementation ได้อย่างไร และถ้าคุณเรียกใช้ Unit Test ConfigurationManager.AppSettings ["User"] การดำเนินการนี้จะไม่ทดสอบหน่วย แต่จะทดสอบว่ามีการดึงค่าใดจากไฟล์กำหนดค่าซึ่งไม่ใช่การทดสอบหน่วย หากคุณต้องการตรวจสอบการใช้งานโปรดดูที่ @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov

174

ฉันใช้ AspnetMvc4 เมื่อสักครู่ที่ผ่านมาฉันเขียน

ConfigurationManager.AppSettings["mykey"] = "myvalue";

ในวิธีการทดสอบของฉันและทำงานได้อย่างสมบูรณ์

คำอธิบาย: วิธีการทดสอบทำงานในบริบทที่มีการตั้งค่าแอพที่นำมาจากโดยทั่วไปคือweb.configหรือmyapp.config. ConfigurationsManagerสามารถเข้าถึงวัตถุทั่วโลกของแอปพลิเคชันและจัดการกับมัน

แม้ว่า: หากคุณมีนักวิ่งทดสอบวิ่งทดสอบควบคู่กันไปนี่ไม่ใช่ความคิดที่ดี


8
นี่เป็นวิธีที่ชาญฉลาดและง่ายในการแก้ปัญหา! ขอชื่นชมความเรียบง่าย!
Navap

1
ง่ายกว่าการสร้างนามธรรมในกรณีส่วนใหญ่
Michael Clark

2
แค่นั้นแหละ???? ความฉลาดอยู่ในความเรียบง่ายขณะที่ฉันกำลังฝึกสมองเกี่ยวกับวิธีทดสอบคลาสที่ปิดผนึกโดยเฉพาะนี้
Piotr Kula

6
ConfigurationManager.AppSettingsเป็นสิ่งNameValueCollectionที่ไม่ปลอดภัยต่อเธรดดังนั้นการทดสอบแบบขนานโดยใช้การซิงโครไนซ์ที่เหมาะสมจึงไม่ใช่ความคิดที่ดี มิฉะนั้นคุณสามารถโทรConfigurationManager.AppSettings.Clear()เข้าTestInitialize/ ctor ของคุณและคุณเป็นสีทอง
Ohad Schneider

1
เรียบง่ายและกระชับ คำตอบที่ดีที่สุด!
znn

21

อาจไม่ใช่สิ่งที่คุณต้องทำให้สำเร็จ แต่คุณคิดจะใช้ app.config ในโครงการทดสอบของคุณหรือไม่ ดังนั้น ConfigurationManager จะได้รับค่าที่คุณใส่ไว้ใน app.config และคุณไม่จำเป็นต้องเยาะเย้ยอะไร วิธีนี้ใช้ได้ดีกับความต้องการของฉันเพราะฉันไม่จำเป็นต้องทดสอบไฟล์ config "ตัวแปร"


7
หากลักษณะการทำงานของโค้ดภายใต้การทดสอบเปลี่ยนแปลงไปตามค่าของค่าการกำหนดค่าการทดสอบจะง่ายกว่าอย่างแน่นอนหากไม่ขึ้นอยู่กับ AppSettings โดยตรง
Andre Loker

2
นี่เป็นการปฏิบัติที่ไม่ดีเนื่องจากคุณไม่เคยทดสอบการตั้งค่าอื่น ๆ ที่เป็นไปได้ คำตอบของ Joshua Enfield นั้นยอดเยี่ยมสำหรับการทดสอบ
mkaj

4
ในขณะที่คนอื่น ๆ ไม่เห็นด้วยกับคำตอบนี้ แต่ฉันก็บอกได้ว่าตำแหน่งของพวกเขานั้นค่อนข้างกว้าง นี่เป็นคำตอบที่ถูกต้องมากในบางสถานการณ์และขึ้นอยู่กับสิ่งที่คุณต้องการจริงๆ ตัวอย่างเช่นสมมติว่าฉันมี 4 คลัสเตอร์ที่แตกต่างกันแต่ละคลัสเตอร์มี URL พื้นฐานที่แตกต่างกัน คลัสเตอร์ทั้ง 4 เหล่านี้จะถูกดึงในระหว่างรันไทม์จากWeb.configโปรเจ็กต์ที่ล้อมรอบ ในระหว่างการทดสอบการดึงค่าที่รู้จักกันดีจากค่าapp.configนั้นใช้ได้มาก การทดสอบหน่วยเพียงแค่ต้องตรวจสอบให้แน่ใจว่าเงื่อนไขเปิดเมื่อดึงออกมาว่า "cluster1" ทำงานได้ ในกรณีนี้มีคลัสเตอร์ที่แตกต่างกันเพียง 4 กลุ่มเท่านั้น
Mike Perrenoud

14

คุณสามารถใช้ shims เพื่อปรับเปลี่ยนAppSettingsเป็นNameValueCollectionวัตถุที่กำหนดเอง นี่คือตัวอย่างของวิธีที่คุณสามารถบรรลุสิ่งนี้:

[TestMethod]
public void TestSomething()
{
    using(ShimsContext.Create()) {
        const string key = "key";
        const string value = "value";
        ShimConfigurationManager.AppSettingsGet = () =>
        {
            NameValueCollection nameValueCollection = new NameValueCollection();
            nameValueCollection.Add(key, value);
            return nameValueCollection;
        };

        ///
        // Test code here.
        ///

        // Validation code goes here.        
    }
}

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการชิมและปลอมที่, Isolating รหัสภายใต้การทดสอบกับ Microsoft ปลอม หวังว่านี่จะช่วยได้


6
ผู้เขียนขอ how to กับ moq ไม่ใช่เรื่อง MS Fakes
JPCF

6
แล้วนี่มันต่างกันยังไง? มันประสบความสำเร็จในการล้อเลียนโดยการลบการพึ่งพาข้อมูลออกจากรหัสของเขา การใช้ C # Fakes เป็นแนวทางหนึ่ง!
Zorayr

9

คุณเคยคิดว่าการลอกเลียนแบบแทนที่จะล้อเลียนหรือไม่? AppSettingsคุณสมบัติเป็นNameValueCollection:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Arrange
        var settings = new NameValueCollection {{"User", "Otuyh"}};
        var classUnderTest = new ClassUnderTest(settings);

        // Act
        classUnderTest.MethodUnderTest();

        // Assert something...
    }
}

public class ClassUnderTest
{
    private readonly NameValueCollection _settings;

    public ClassUnderTest(NameValueCollection settings)
    {
        _settings = settings;
    }

    public void MethodUnderTest()
    {
        // get the User from Settings
        string user = _settings["User"];

        // log
        Trace.TraceInformation("User = \"{0}\"", user);

        // do something else...
    }
}

ข้อดีคือการใช้งานที่ง่ายกว่าและไม่ต้องพึ่งพา System.Configuration จนกว่าคุณจะต้องการจริงๆ


3
ฉันชอบแนวทางนี้ที่สุด ในแง่หนึ่งการรวมตัวจัดการการกำหนดค่าด้วยIConfigurationตามที่ Joshua Enfield แนะนำอาจเป็นระดับที่สูงเกินไปและคุณอาจพลาดข้อบกพร่องที่มีอยู่เนื่องจากสิ่งต่างๆเช่นการแยกวิเคราะห์ค่าการกำหนดค่าที่ไม่ถูกต้อง ในทางกลับกันการใช้ConfigurationManager.AppSettingsโดยตรงตามที่ LosManos แนะนำเป็นรายละเอียดการใช้งานมากเกินไปไม่ต้องพูดถึงอาจมีผลข้างเคียงกับการทดสอบอื่น ๆ และไม่สามารถใช้ในการทดสอบแบบขนานโดยไม่ต้องซิงโครไนซ์ด้วยตนเอง (เนื่องจากNameValueConnectionเธรดไม่ปลอดภัย)
Ohad Schneider

2

นั่นคือคุณสมบัติคงที่และ Moq ได้รับการออกแบบมาเพื่อวิธีการอินสแตนซ์ Moq หรือคลาสที่สามารถล้อเลียนผ่านการสืบทอด กล่าวอีกนัยหนึ่ง Moq จะไม่ช่วยอะไรคุณที่นี่

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

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

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


1

ฉันคิดว่าการเขียนผู้ให้บริการ app.config ของคุณเป็นงานง่ายๆและมีประโยชน์มากกว่าอย่างอื่น โดยเฉพาะอย่างยิ่งคุณควรหลีกเลี่ยงของปลอมเช่น shims เป็นต้นเพราะทันทีที่คุณใช้มัน Edit & Continue จะไม่ทำงานอีกต่อไป

ผู้ให้บริการที่ฉันใช้มีลักษณะดังนี้:

โดยค่าเริ่มต้นพวกเขาจะได้รับค่าจากApp.configแต่สำหรับการทดสอบหน่วยฉันสามารถแทนที่ค่าทั้งหมดและใช้ในการทดสอบแต่ละครั้งโดยอิสระ

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

public class AppConfigProvider
{
    public AppConfigProvider()
    {
        ConnectionStrings = new ConnectionStringsProvider();
        AppSettings = new AppSettingsProvider();
    }

    public ConnectionStringsProvider ConnectionStrings { get; private set; }

    public AppSettingsProvider AppSettings { get; private set; }
}

public class ConnectionStringsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            if (_customValues.TryGetValue(key, out customValue))
            {
                return customValue;
            }

            var connectionStringSettings = ConfigurationManager.ConnectionStrings[key];
            return connectionStringSettings == null ? null : connectionStringSettings.ConnectionString;
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}

public class AppSettingsProvider
{
    private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string this[string key]
    {
        get
        {
            string customValue;
            return _customValues.TryGetValue(key, out customValue) ? customValue : ConfigurationManager.AppSettings[key];
        }
    }

    public Dictionary<string, string> CustomValues { get { return _customValues; } }
}

1

แค่ตั้งค่าสิ่งที่คุณต้องการล่ะ? เพราะฉันไม่อยากล้อเลียน. NET ฉัน ... ?

System.Configuration.ConfigurationManager.AppSettings["myKey"] = "myVal";

คุณควรล้าง AppSettings ก่อนเพื่อให้แน่ใจว่าแอปเห็นเฉพาะสิ่งที่คุณต้องการ

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