كيف يمكنني أن ريفاكتور هذا المصنع من نوع الأسلوب وقاعدة بيانات الاتصال أن تكون قابلة للاختبار?

StackOverflow https://stackoverflow.com/questions/1233486

سؤال

أنا في محاولة لمعرفة كيفية القيام اختبار وحدة والاستهزاء.لقد فهم بعض المبادئ من TDD و الاختبار الأساسية.ومع ذلك, أنا أبحث في إعادة بيع ديون رمز أدناه التي تم كتابتها بدون اختبارات و أنا أحاول أن أفهم كيف أنه يحتاج إلى تغيير من أجل جعلها قابلة للاختبار.

public class AgentRepository
{

public Agent Select(int agentId)
{
    Agent tmp = null;
    using (IDataReader agentInformation = GetAgentFromDatabase(agentId))
    {
        if (agentInformation.Read())
        {
            tmp = new Agent();
            tmp.AgentId = int.Parse(agentInformation["AgentId"].ToString());
            tmp.FirstName = agentInformation["FirstName"].ToString();
            tmp.LastName = agentInformation["LastName"].ToString();
            tmp.Address1 = agentInformation["Address1"].ToString();
            tmp.Address2 = agentInformation["Address2"].ToString();
            tmp.City = agentInformation["City"].ToString();
            tmp.State = agentInformation["State"].ToString();
            tmp.PostalCode = agentInformation["PostalCode"].ToString();
            tmp.PhoneNumber = agentInformation["PhoneNumber"].ToString();
        }
    }

    return tmp;
}

private IDataReader GetAgentFromDatabase(int agentId)
{
    SqlCommand cmd = new SqlCommand("SelectAgentById");
    cmd.CommandType = CommandType.StoredProcedure;

    SqlDatabase sqlDb = new SqlDatabase("MyConnectionString");
    sqlDb.AddInParameter(cmd, "AgentId", DbType.Int32, agentId);
    return sqlDb.ExecuteReader(cmd);
}

}

هاتين الطريقتين في فئة واحدة.قاعدة البيانات ذات الصلة رمز في GetAgentFromDatabase هو ذات الصلة إلى المؤسسة المكتبات.

كيف وأود أن تكون قادرة على جعل هذه قابلة للاختبار?يجب مجردة من GetAgentFromDatabase طريقة في مختلف الصف ؟ يجب أن GetAgentFromDatabase عودة شيئا آخر غير IDataReader?أي اقتراحات أو مؤشرات إلى الروابط الخارجية سيكون موضع تقدير كبير.

هل كانت مفيدة؟

المحلول

أنت على حق حول الانتقال GetAgentFromDatabase() في فئة منفصلة.هنا كيف يمكنني تعريف AgentRepository:

public class AgentRepository {
    private IAgentDataProvider m_provider;

    public AgentRepository( IAgentDataProvider provider ) {
        m_provider = provider;
    }

    public Agent GetAgent( int agentId ) {
        Agent agent = null;
        using( IDataReader agentDataReader = m_provider.GetAgent( agentId ) ) {
            if( agentDataReader.Read() ) {
                agent = new Agent();
                // set agent properties later
            }
        }
        return agent;
    }
}

حيث حددت IAgentDataProvider واجهة على النحو التالي:

public interface IAgentDataProvider {
    IDataReader GetAgent( int agentId );
}

لذلك ، AgentRepository هي فئة تحت الاختبار.سوف وهمية IAgentDataProvider وحقن التبعية.(أنا فعلت هذا مع موك, ولكن يمكنك بسهولة إعادة عليه مع مختلف العزلة الإطار).

[TestFixture]
public class AgentRepositoryTest {
    private AgentRepository m_repo;
    private Mock<IAgentDataProvider> m_mockProvider;

    [SetUp]
    public void CaseSetup() {
        m_mockProvider = new Mock<IAgentDataProvider>();
        m_repo = new AgentRepository( m_mockProvider.Object );
    }

    [TearDown]
    public void CaseTeardown() {
        m_mockProvider.Verify();
    }

    [Test]
    public void AgentFactory_OnEmptyDataReader_ShouldReturnNull() {
        m_mockProvider
            .Setup( p => p.GetAgent( It.IsAny<int>() ) )
            .Returns<int>( id => GetEmptyAgentDataReader() );
        Agent agent = m_repo.GetAgent( 1 );
        Assert.IsNull( agent );
    }

    [Test]
    public void AgentFactory_OnNonemptyDataReader_ShouldReturnAgent_WithFieldsPopulated() {
        m_mockProvider
            .Setup( p => p.GetAgent( It.IsAny<int>() ) )
            .Returns<int>( id => GetSampleNonEmptyAgentDataReader() );
        Agent agent = m_repo.GetAgent( 1 );
        Assert.IsNotNull( agent );
                    // verify more agent properties later
    }

    private IDataReader GetEmptyAgentDataReader() {
        return new FakeAgentDataReader() { ... };
    }

    private IDataReader GetSampleNonEmptyAgentDataReader() {
        return new FakeAgentDataReader() { ... };
    }
}

(تركت تنفيذ الدرجة FakeAgentDataReader, الذي ينفذ IDataReader و هو تافهة -- تحتاج فقط إلى تنفيذ قراءة() و Dispose() لجعل الاختبارات العمل.)

الغرض من AgentRepository هنا هو أن تأخذ IDataReader الكائنات وتحويلها إلى شكلت بشكل صحيح الوكيل الكائنات.يمكنك تعظيم أعلاه المباراة اختبار لاختبار حالات أكثر إثارة للاهتمام.

بعد الوحدة الاختبار AgentRepository بمعزل عن قاعدة البيانات الفعلية, سوف تحتاج وحدة اختبارات الخرسانة تنفيذ IAgentDataProvider, ولكن هذا موضوع منفصل السؤال.HTH

نصائح أخرى

المشكلة هنا هي تحديد ما هو SUT وما هو الاختبار.مع المثال الخاص بك تحاول اختبار Select() الطريقة وبالتالي يريدون أن عزل من قاعدة البيانات.لديك العديد من الخيارات ،

  1. Virtualise على GetAgentFromDatabase() إذا كنت يمكن أن توفر فئة مشتقة مع رمز العودة إلى القيم الصحيحة في هذه الحالة إنشاء كائن التي توفر IDataReaderFunctionaity دون التحدث إلى أي DB

    class MyDerivedExample : YourUnnamedClass
    {
        protected override IDataReader GetAgentFromDatabase()
        {
            return new MyDataReader({"AgentId", "1"}, {"FirstName", "Fred"},
              ...);
        }
    }
    
  2. كما Gishu اقترح بدلا من استخدام عيسى العلاقات (الميراث) استخدام الأحساء (تكوين الكائن) حيث كنت مرة أخرى فئة مقابض خلق وهمية IDataReader, ولكن هذه المرة دون وراثة.

    ولكن كل من هذه النتيجة في الكثير من التعليمات البرمجية التي ببساطة يحدد مجموعة من النتائج التي يتم إرجاعها عند الاستعلام.المسلم نتمكن من الحفاظ على هذا الرمز في اختبار الكود بدلا من الكود ولكن جهد.كل ما تقومون به حقا هو تحديد مجموعة نتائج معينة الاستعلامات, وأنت تعرف ما هو جيد حقا في القيام بذلك...قاعدة البيانات

  3. اعتدت LinqToSQL حين يعود واكتشفت أن DataContext كائنات لديها بعض الأساليب المفيدة ، بما في ذلك DeleteDatabase و CreateDatabase.

    public const string UnitTestConnection = "Data Source=.;Initial Catalog=MyAppUnitTest;Integrated Security=True";
    
    
    [FixtureSetUp()]
    public void Setup()
    {
      OARsDataContext context = new MyAppDataContext(UnitTestConnection);
    
      if (context.DatabaseExists())
      {
        Console.WriteLine("Removing exisitng test database");
        context.DeleteDatabase();
      }
      Console.WriteLine("Creating new test database");
      context.CreateDatabase();
    
      context.SubmitChanges();
    }
    

النظر في ذلك لفترة من الوقت.المشكلة مع استخدام قاعدة بيانات وحدة الاختبارات هو أن البيانات سوف تتغير.حذف قاعدة البيانات الخاصة بك واستخدام الاختبارات أن تتطور البيانات الخاصة بك التي يمكن استخدامها في التجارب القادمة.

هناك نوعان من الأشياء أن تكون حذرا من تأكد من الاختبارات الخاصة بك تشغيل في الترتيب الصحيح.على MbUnit الجملة من أجل هذا [DependsOn("NameOfPreviousTest")].تأكد من مجموعة واحدة فقط من اختبارات يعمل ضد قاعدة بيانات معينة.

سأبدأ في طرح بعض الأفكار و سيتم تحديث على طول الطريق:

  • SqlDatabase sqlDb = new SqlDatabase("MyConnectionString");- يجب تجنب جديد شركات مختلطة مع المنطق.يجب بناء xor يكون منطق العمليات ؛ تجنب حدوثها في نفس الوقت.استخدام حقن التبعية لتمرير قاعدة البيانات هذه كمعلمة ، حتى تستطيع أن تسخر منه.أعني هذا إذا كنت ترغب في وحدة اختبار (لن لقاعدة البيانات والتي يجب أن يتم في بعض الحالات في وقت لاحق)
  • IDataReader agentInformation = GetAgentFromDatabase(agentId) - ربما منفصلة القارئ استرجاع بعض فئة أخرى ، لذلك يمكنك أن تسخر هذه الفئة أثناء اختبار المصنع رمز.

المنظمة البحرية الدولية يجب أن عادة إلا للقلق حول جعل الممتلكات العامة/الأساليب قابلة للاختبار.أولا-هاء.طالما حدد(الباحث agentId) يعمل عادة لا أهتم كيف يفعل ذلك عن طريق GetAgentFromDatabase(الباحث agentId).

ما كنت قد يبدو معقولا ، كما أتصور أنه يمكن اختبارها مع شيء من هذا القبيل ما يلي (على افتراض صفك يسمى AgentRepository)

AgentRepository aRepo = new AgentRepository();
int agentId = 1;
Agent a = aRepo.Select(agentId);
//Check a here

كما اقترح التحسينات.أنصح السماح AgentRepository هي سلسلة الاتصال إلى تغيير ، سواء من قبل الجمهور أو الوصول الداخلي.

على افتراض أن كنت في محاولة لاختبار العامة حدد طريقة الدرجة [بدون اسم]..

  1. نقل GetAgentFromDatabase (طريقة) في واجهة أقول IDB_Access.اسمحوا NoName يكون واجهة الأعضاء التي يمكن تعيينها باعتباره المنشئ المعلمة أو الممتلكات.حتى الآن لديك التماس ، يمكنك تغيير السلوك دون تعديل التعليمات البرمجية في الأسلوب.
  2. أود تغيير عودة نوع من الطريقة المذكورة أعلاه للعودة شيء أكثر العامة - يبدو أنك استخدامه مثل hashtable.اسمحوا إنتاج تنفيذ IDB_Access استخدام IDataReader لخلق hashtable داخليا.كما يجعلها أقل تقنية تعتمد ;لا يمكن تنفيذ هذه الواجهة باستخدام الخلية أو بعض غير MS/.صافي البيئة.private Hashtable GetAgentFromDatabase(int agentId)
  3. القادم الخاص بك اختبار الوحدة ، يمكنك العمل مع كعب (أو استخدام شيء أكثر تقدما مثل إطار وهمية)

.

public MockDB_Access : IDB_Access
{
  public const string MY_NAME = "SomeName;
  public Hashtable GetAgentFromDatabase(int agentId)
  {  var hash = new Hashtable();
     hash["FirstName"] = MY_NAME; // fill other properties as well
     return hash;
  }
}

// in the unit test
var testSubject = new NoName( new MockDB_Access() );
var agent = testSubject.Select(1);
Assert.AreEqual(MockDB_Access.MY_NAME, agent.FirstName); // and so on...

وأما بالنسبة لرأيي يجب ألا testet طريقة GetAgentFromDatabase () من خلال إجراء اختبار إضافي، لأن رمزها مغطاة بالكامل من قبل اختبار طريقة تحديد (). لا توجد فروع رمز يستطيع المشي على طول، لذلك لا جدوى من خلق اختبار إضافي هنا. إذا تم استدعاء الأسلوب GetAgentFromDatabase () من أساليب متعددة يجب اختبار ذلك من تلقاء نفسها بالرغم من ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top