Non riesco a capire in che modo l'injection dependency può facilitare i test all'interno di asp.net MVC

0

Ogni volta che leggo un articolo o un libro su asp.net MVC, ci sarà un capitolo per l'iniezione delle dipendenze e il repository. ora posso facilmente percepire i vantaggi dell'utilizzo del repository rispetto all'hard coding del codice del framework di entità all'interno dei metodi di azione, poiché utilizzando il repository posso scrivere i metodi che usano il framework di entità all'interno dei miei metodi di repository e riutilizzare questi metodi sul mio metodo di azione, quindi può facilmente capire che l'uso del repository faciliterà la riutilizzabilità.

ma quando si parla di iniezione di dipendenza, gli autori dicono sempre che facilita il test e il test unitario. quindi dicono invece di avviare la classe repository all'interno della classe controller, avvierò un'interfaccia della classe repository. e dicono che questo faciliterà il test dell'unità .. ma non riesco a pensarci, principalmente perché l'imitazione dell'interfaccia del repository all'interno della mia classe controller faciliterà il testing delle unità mentre si avvia la classe del repository stessa all'interno della mia classe controller non !!

Qualcuno può dirlo su questo?

    
posta john Gu 26.06.2017 - 18:03
fonte

2 risposte

2

Considera un semplice blog e un PostsController che mostra un post sul blog.

Abilita l'integrazione delle dipendenze impostando l'oggetto IPostRepository usando un argomento costruttore e invece di istanziare una classe concreta nel controller:

public class PostsController : Controller
{
    private IPostRepository posts;

    public PostsController(IPostRepository posts)
    {
        if (posts == null)
            throw new ArgumentNullException("posts");

        this.posts = posts;
    }

    public ActionResult Details(int id)
    {
        BlogPost post = posts.Find(id);

        if (post == null)
            return HttpNotFound();

        BlogPostDetails model = new BlogPostDetails(post);

        return View(model);
    }
}

Consentendo l'iniezione del repository dei post, possiamo testare unitamente la logica nel controller fornendo un oggetto "simulato". In questo caso, sto usando Moq , una libreria di derisione degli oggetti disponibile su NuGet (non un avallo, solo per il scopo di completare l'esempio):

[TestClass]
public class BlogPostTests
{
    private IPostRepository mockRepository;

    [TestInitialize]
    public void Setup()
    {
        this.mockRepository = new Moq.Mock<IPostRepository>();
    }

    [TestMethod]
    public void ReturnsView()
    {
        var expectedPost = new BlogPost("Test Title", "<p>Body text</p>", DateTime.Parse("2017/01/13"));

        mockRepository.Setup(posts => posts.Find(1))
            .Returns(expectedPost) // posts.Find(1) will return a test stub
            .Verifiable(); // If posts.Find(1) doesn't get called, test fails

        var controller = new PostsController(mockRepository.Object);
        var result = controller.Details(1);

        // Assert that correct view was returned
    }
}

Ora puoi scollegare il controller dal resto dello stack tecnologico e basta testare il metodo "Details", rendendolo un vero Unit Test. Come ulteriore vantaggio, questo test verrà eseguito in un millisecondo o meno. Confrontalo con un test funzionale manuale eseguito da un umano, che probabilmente impiegherà più di un minuto per impostare i dati e verificarlo, o 20-30 secondi per un test funzionale automatizzato. Il test dell'unità verrà eseguito più velocemente di un ordine di grandezza.

    
risposta data 26.06.2017 - 18:52
fonte
1

Il punto di test unitario è di isolare tutte le dipendenze. Altrimenti non sei un test unitario, sei un test di integrazione.

Il problema

Quindi come isolare le dipendenze in modo da testare solo il codice di interesse? Bene diamo un'occhiata.

Considera il seguente esempio di controller / azione:

class MyController
{
    public ActionResult MyAction()
    {
        if (HttpContext.Current.Request.RawUrl.Contains("SomeString")
        {
            return Redirect("/Someurl");
        }
        return View();
    }
}

Questo è un codice terribile per una serie di motivi (vincolo del modello, chiunque?) ... ma per la discussione attuale, mettilo da parte e osserva che HttpContext viene creato dal metodo di azione tramite una sorta di singleton modello. Non è iniettato e non c'è modo di cambiarlo, se non quello di modificare il codice o fare qualcosa di veramente strano con l'API di profilazione .NET, come fa TypeMock. Senza sforzi eroici, sei bloccato usando il vero oggetto HttpContext nel runtime .NET.

Ora immagina di voler scrivere un test unitario per vedere se il metodo di azione esegue effettivamente un reindirizzamento quando l'URL contiene "SomeString". Come lo faresti? Dove prendi HttpContext? Ne hai bisogno di uno vero. Quindi fai girare un server web sulla tua macchina per testare le unità? Richiamalo con un client web? Dovrai iscriverti? Creare i cookie? Ugh.

Bottom line: Non hai modo di isolare la dipendenza da HttpContext.

Un modo migliore

Se dovessimo modernizzarlo con un po 'di DI, potrebbe apparire come questo:

class MyController
{
    private readonly HttpContextBase _httpContext;

    public MyController(HttpContextBase context)  //Injected
    {
        _httpContext = context;
    }

    public ActionResult MyAction()
    {
        if (_httpContext.Request.RawUrl.Contains("SomeString")
        {
            return Redirect("/Someurl");
        }
        return View();
    }
}

Ora possiamo testare questo, fornendo un mockup del contesto HTTP conforme all'interfaccia di HttpContextBase, come questo:

class MockHttpRequest: System.Web.HttpRequestBase
{
    public override string RawUrl
    {
        get
        {
            return "SomeString";
        }
    }
}
class MockHttpContext : System.Web.HttpContextBase
{
    public override System.Web.HttpRequestBase Request
    {
        get
        {
            return new MockHttpRequest();
        }
    }
}

E nel codice di test dell'unità:

//Arrange
var context = new MockHttpContext();
var controller = new MyController(context);

//Act
var result = controller.MyAction();

//Assert
Assert.IsTrue(result is RedirectResult);

Pezzo di torta. Non serve un server web. Non hai nemmeno bisogno di Moq o TypeMock o Fakes. Puoi fare tutto da solo. Non sarebbe solo più difficile, ma sarebbe impossibile nel modo tradizionale, senza una struttura beffarda.

P.S.

Per inciso, Microsoft ha pensato che fosse così prezioso che hanno aggiunto HttpContextBase solo per poter essere iniettato e sostituito nei test di unità (il normale HttpContext è sigillato). Se si sono presi la briga di aggiungerlo al runtime, probabilmente vale la pena utilizzarlo.

    
risposta data 27.06.2017 - 04:34
fonte