Come simulare il metodo con un oggetto hard coded?

11

Sto lavorando su un'applicazione che ha più livelli. Livello di accesso ai dati per recuperare e salvare i dati dall'origine dati, logica aziendale per manipolare i dati, interfaccia utente per mostrare i dati sullo schermo.

Eseguo anche test unitari del livello della logica aziendale. L'unico requisito è testare il flusso della logica del livello aziendale. Quindi uso il framework Moq per deridere il livello di accesso ai dati e l'unità testare il livello della business logic con MS Unit.

Sto usando la programmazione dell'interfaccia per rendere il disaccoppiamento il più possibile possibile, in modo da poter eseguire il test dell'unità. Livello di accesso ai dati delle chiamate del livello aziendale tramite l'interfaccia.

Sto affrontando un problema quando sto provando a testare uno dei metodi di business logic. Quel metodo fa un po 'di lavoro e crea un oggetto e lo passa al livello di accesso ai dati. Quando sto cercando di prendere in giro il metodo del livello di accesso ai dati, allora non può simulare con successo.

Qui sto cercando di creare un codice demo per mostrare il mio problema.

Modello:

public class Employee
{
    public string Name { get; set; }
}

Livello di accesso ai dati:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

Livello della logica aziendale:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

Test unitario:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

Nel caso di test unitario al momento della simulazione sto inviando un oggetto Employee ma quando si richiama il metodo della business logic, si sta creando un oggetto Employee diverso all'interno del metodo. Ecco perché non riesco a deridere l'oggetto.

In tal caso come progettare in modo da poter risolvere il problema?

    
posta DeveloperArnab 17.12.2013 - 18:46
fonte

3 risposte

11

Invece di creare un oggetto Employee direttamente usando new , la tua classe Bll potrebbe usare una classe EmployeeFactory per questo, con un metodo createInstance , che viene iniettato tramite il costruttore:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

Il costruttore dovrebbe prendere l'oggetto factory tramite un'interfaccia IEmployeeFactory , quindi puoi sostituire la fabbrica "reale" facilmente con una fabbrica fittizia.

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

La fabbrica fittizia può fornire al test qualsiasi tipo di oggetto Employee necessario per il test (ad esempio, createInstance potrebbe sempre restituire lo stesso oggetto):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

Ora usare questo mock nel test dovrebbe fare il trucco.

    
risposta data 17.12.2013 - 19:49
fonte
4

Lo tratterei come unità singola da testare.

Finché controlli tutti gli input da cui viene creato l'oggetto Employee , il fatto che sia creato nell'oggetto testato non dovrebbe avere importanza. Hai solo bisogno di un metodo simulato per restituire il risultato previsto se il contenuto dell'argomento corrisponde alle aspettative.

Ovviamente significa che è necessario fornire una logica personalizzata per il metodo di simulazione. La logica avanzata spesso non può essere testata con il solo tipo "per x return y" di mock.

In effetti, dovresti non fare in modo che restituisca oggetti diversi nei test rispetto a quelli in produzione, perché se lo facessi, non testerai il codice che dovrebbe crearlo. Ma quel codice è parte integrante del codice di produzione e quindi dovrebbe essere coperto anche dal caso di test.

    
risposta data 17.12.2013 - 21:35
fonte
0

È un fallimento di alcuni strumenti di test, devi sempre usare le interfacce e tutto deve essere creato in un modo che permetta di scambiare l'oggetto basato sull'interfaccia con un altro.

Tuttavia, ci sono strumenti migliori: prendi Microsoft Fakes (era chiamato Moles) che ti permette di scambiare qualsiasi oggetto, anche statico e globale. Ci vuole un approccio di livello più basso per sostituire gli oggetti in modo da non dover utilizzare le interfacce ovunque, pur mantenendo il modo di scrivere i test a cui sei abituato.

    
risposta data 20.12.2013 - 21:00
fonte

Leggi altre domande sui tag