Frage

Ich versuche zu lernen, wie Unit Testing und Mocking zu tun. Ich verstehe einige der Prinzipien der TDD und grundlegende Tests. Aber ich bin auf der Suche den Code unten auf Refactoring, die ohne Tests geschrieben wurde und ich versuche zu verstehen, wie es ändern, um muss es testbar zu machen.

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);
}

}

Diese beiden Methoden in einer Klasse sind. Die Datenbank-bezogenen Code in der GetAgentFromDatabase bezieht sich auf Enterprise-Bibliotheken.

Wie würde ich in der Lage sein, über die Herstellung dieses prüfbar zu gehen? Soll ich abstrakt aus der GetAgentFromDatabase Methode in eine andere Klasse? Sollte GetAgentFromDatabase etwas anderes als ein IDataReader zurückkehren? Irgendwelche Vorschläge oder Hinweise auf externe Links würde sehr geschätzt werden.

War es hilfreich?

Lösung

Sie haben recht zu bewegen GetAgentFromDatabase () in eine eigene Klasse. Hier ist, wie ich neu definiert 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;
    }
}

, wo ich die definiert IAgentDataProvider Schnittstelle wie folgt:

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

So AgentRepository ist die Klasse im Test. Wir spotten IAgentDataProvider und die Abhängigkeit injizieren. (Ich habe es mit Moq , aber man kann es leicht mit einem anderen Isolations Rahmen Redo).

[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() { ... };
    }
}

(verließ ich die Implementierung der Klasse aus FakeAgentDataReader , die Arbeitsgeräte IDataReader und trivial ist - Sie brauchen nur zu implementieren Read () und Dispose () , um die Tests Arbeit zu machen.)

Der Zweck der AgentRepository ist hier nehmen IDataReader Objekte und verwandeln sie in richtig gebildet Agenten Objekte. Sie können die oben Prüfadapter erweitern interessantere Fälle zu testen.

Nach Komponententests AgentRepository in Isolation von der eigentlichen Datenbank, müssen Sie Unit-Tests für eine konkrete Umsetzung von IAgentDataProvider , aber das ist ein Thema für eine andere Frage. HTH

Andere Tipps

Das Problem hier ist zu entscheiden, was SUT ist und was-Test. Mit Ihrem Beispiel versuchen Sie, die Select() Methode zu testen und damit, dass aus der Datenbank isolieren wollen. Sie haben mehrere Möglichkeiten,

  1. die GetAgentFromDatabase() Virtualisieren, so dass Sie eine abgeleitete Klasse mit dem Code zur Verfügung stellen können, die richtigen Werte zurück, in diesem Fall ein Objekt zu schaffen, IDataReaderFunctionaity ohne im Gespräch mit der DB d

    bietet
    class MyDerivedExample : YourUnnamedClass
    {
        protected override IDataReader GetAgentFromDatabase()
        {
            return new MyDataReader({"AgentId", "1"}, {"FirstName", "Fred"},
              ...);
        }
    }
    
  2. vorgeschlagen Gishu statt IsA Beziehungen (Vererbung) des Verwendens verwenden Hasa (Objekt-Komposition), wo Sie noch einmal eine Klasse, die ein Mock IDataReader Griffe zu schaffen, aber diesmal ohne erben.

    Doch diese beiden Ergebnisse in vielen Code, der einfach einen Satz von Ergebnissen definiert, die wir zurückgeschickt werden, wenn abgefragt. Zwar können wir diesen Code in den Prüfregeln halten, statt unseren Haupt-Code, aber sein Bemühen. Alles, was Sie wirklich tun, ist ein Ergebnis für bestimmte Abfragen festlegen definieren, und Sie wissen, was das ... Eine Datenbank zu tun

  3. wirklich gut
  4. Ich habe LinqToSQL eine Weile zurück und entdeckt, dass die DataContext Objekte einige sehr nützliche Methoden, einschließlich DeleteDatabase und 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();
    }
    

Betrachten Sie es für eine Weile. Das Problem mit einer Datenbank für Unit-Tests besteht darin, dass die Daten ändern. Löschen Sie Ihre Datenbank und verwenden Sie Ihre Tests Ihre Daten zu entwickeln, die in zukünftigen Tests verwendet werden können.

Es gibt zwei Dinge, vorsichtig zu sein Stellen Sie sicher, dass Ihre Tests in der richtigen Reihenfolge ausgeführt werden. Die MbUnit Syntax hierfür ist [DependsOn("NameOfPreviousTest")]. Achten Sie darauf, nur eine Reihe von Tests ausgeführt wird gegen eine bestimmte Datenbank.

Ich werde beginnen, einige Ideen setzen und auf dem Weg aktualisieren:

  • SQLDatabase sqldb = new SQLDatabase ( "myConnectionString"); - Sie sollten es vermeiden, neue Operatoren mit Logik gemischt. Sie sollten xor haben logische Operationen konstruieren; vermeiden, dass sie zur gleichen Zeit passiert. Verwenden Sie Dependency Injection diese Datenbank als Parameter übergeben, so dass Sie es verspotten. Ich meine, das, wenn Sie Unit-Test wollen es (nicht in die Datenbank gehen, die später in einigen Fällen getan werden sollte)
  • IDataReader agentInformation = GetAgentFromDatabase (AgentID) -. Vielleicht den Reader Abruf einer anderen Klasse, die Sie könnten trennen, so können Sie diese Klasse verspotten, während der Werkscode testen

IMO sollten Sie normalerweise nur Sorgen über Ihre öffentliche Eigenschaften / Methoden überprüfbar zu machen. D. h solange Wählen Sie (int AgentID) arbeitet man normalerweise egal, wie sie es tut über GetAgentFromDatabase (int AgentID) .

Was haben Sie scheint vernünftig, wie ich mit etwas getestet werden wie folgt aus (vorausgesetzt, Ihre Klasse AgentRepository genannt wird) vorstellen kann es

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

Wie für vorgeschlagene Verbesserungen. Ich würde empfehlen, die AgentRepository der Verbindungszeichenfolge ermöglicht geändert werden, entweder von öffentlichen oder internen Zugriff.

Unter der Annahme, dass Sie versuchen, die öffentliche Select-Methode der Klasse zu testen [NoName] ..

  1. Bewegen Sie den GetAgentFromDatabase () -Methode in eine Schnittstelle sagen IDB_Access. Lassen Sie NoName eine Schnittstelle Mitglied haben, das als Ctor Parameter oder eine Eigenschaft festgelegt werden kann. So, jetzt eine Naht haben, können Sie das Verhalten ändern, ohne den Code in das Verfahren zu ändern.
  2. würde ich den Rückgabetyp des oben genannten Verfahrens ändern etwas allgemeinere zurückzukehren - scheinen Sie es wie eine Hash-Tabelle zu verwenden. Lassen Sie die Produktion Umsetzung der IDB_Access die IDataReader verwenden, um die Hash-Tabelle intern zu erstellen. Es macht es auch weniger technologieabhängig; Ich kann diese Schnittstelle Umgebung mit MySql oder eine Nicht-MS / .net implementieren. private Hashtable GetAgentFromDatabase(int agentId)
  3. Weiter für Ihr Gerät zu testen, können Sie mit einem Stummel arbeiten (oder etwas weiter fortgeschritten wie ein Mock-Framework verwenden)

.

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...

Was meiner Meinung nach die GetAgentFromDatabase () Methode darf nicht durch einen zusätzlichen Test getestet werden, da ihr Code vollständig durch den Test des Select () -Methode bedeckt ist. Es gibt keine Verzweigungen der Code zu Fuß entlang konnte, also keinen Sinn, bei der Schaffung von hier einen zusätzlichen Test. Wenn die GetAgentFromDatabase () -Methode von mehreren Methoden aufgerufen wird, sollten Sie es auf seinem eigenen testen though.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top