Come deridere il metodo con oggetto hard coded?


11

Sto lavorando a 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.

Faccio anche test unitari del livello di 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 testare il livello di logica aziendale con MS Unit.

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

Sto riscontrando un problema quando provo a testare uno dei metodi della logica aziendale. Tale metodo funziona e crea un oggetto e lo passa al livello di accesso ai dati. Quando sto cercando di deridere quel metodo del livello di accesso ai dati, allora non riesco a deridere 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 di 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 unit test al momento del derisione, sto inviando un oggetto Employee ma quando invoco il metodo della logica aziendale, sta creando un diverso oggetto Employee all'interno del metodo. Ecco perché non riesco a deridere l'oggetto.

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


Di solito il trucco è avvolgere l'oggetto in un'interfaccia e fare in modo che tutti i consumatori utilizzino quell'interfaccia, quindi basta deridere l'interfaccia, in alternativa è possibile rendere virtuale il metodo e quindi moq può deridere il metodo senza l'interfaccia. Non sono sicuro di rinoceronti o altri in questo caso, tuttavia.
Jimmy Hoffa,

Risposte:


12

Invece di creare un Employeeoggetto direttamente usando new, la tua classe Bllpotrebbe usare una EmployeeFactoryclasse per questo, con un metodo createInstance, che viene iniettato attraverso il costruttore:

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

Il costruttore dovrebbe prendere l'oggetto factory attraverso un'interfaccia IEmployeeFactory, in modo da poter sostituire facilmente la fabbrica "reale" con una factory finta.

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 di simulazione può fornire al test qualsiasi tipo di Employeeoggetto necessario per il test (ad esempio, createInstancepotrebbe 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 finto nel tuo test dovrebbe fare il trucco.


Puoi darmi un esempio di codice, in modo da poter visualizzare la tua teoria?
DeveloperArabab,

@DeveloperArnab: vedi la mia modifica.
Doc Brown,

Molto utile ...
DeveloperArnab il

4

Lo tratterei come singola unità da testare.

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

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

In realtà, si dovrebbe non farla tornare oggetto diverso nelle prove di quello che sarà in produzione, perché se così fosse, non sareste testando il codice che dovrebbe crearlo. Ma quel codice è parte integrante del codice di produzione e quindi dovrebbe essere coperto anche dal test case.


Sì, non mi preoccupo degli input del livello di accesso ai dati, voglio solo deridere quell'oggetto e restituire un dato codificato in modo da poter testare la logica aziendale. Ma il problema è a causa di due diversi oggetti Employee, non riesco a deridere il metodo del livello di accesso ai dati.
DeveloperArnab il

@DeveloperArnab: gli oggetti saranno diversi, ma avranno contenuto noto. Quindi tutto ciò che devi fare è fare un finto confronto personalizzato anziché l'identità dell'oggetto.
Jan Hudec,

@DeveloperArnab: se si iniettano Employeeoggetti diversi nei test, non si verificherà il codice che normalmente lo crea. Quindi non dovresti cambiarlo.
Jan Hudec,

0

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

Tuttavia, ci sono strumenti migliori: prendi Microsoft Fakes (si chiamava Moles) che ti consente di scambiare qualsiasi oggetto, anche statico e globale. Adotta un approccio di livello più basso per sostituire gli oggetti in modo da non dover usare le interfacce ovunque, pur mantenendo il modo di scrivere i test a cui siete abituati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.