2008-12-11 18 views
7

Así que soy un novato en TDD, y creé con éxito una pequeña y bonita aplicación de muestra utilizando el patrón MVP. El problema principal de mi solución actual es que bloquea el hilo de la interfaz de usuario, así que estaba intentando configurar el presentador para usar SynchronizationContext.Current, pero cuando ejecuto mis pruebas, SynchronizationContext.Current es nulo.TDD Refactorización de prueba para admitir MultiThreading

Presentador antes de enhebrar

public class FtpPresenter : IFtpPresenter 
{ 
    ... 
    void _view_GetFilesClicked(object sender, EventArgs e) 
    { 
     _view.StatusMessage = Messages.Loading; 

     try 
     { 
      var settings = new FtpAuthenticationSettings() 
      { 
       Site = _view.FtpSite, 
       Username = _view.FtpUsername, 
       Password = _view.FtpPassword 
      }; 
      var files = _ftpService.GetFiles(settings); 

      _view.FilesDataSource = files; 
      _view.StatusMessage = Messages.Done;   
     } 
     catch (Exception ex) 
     { 
      _view.StatusMessage = ex.Message; 
     } 
    } 
    ... 
} 

de prueba antes de enhebrar

[TestMethod] 
public void Can_Get_Files() 
{ 
    var view = new FakeFtpView(); 
    var presenter = new FtpPresenter(view, new FakeFtpService(), new FakeFileValidator()); 

    view.GetFiles(); 
    Assert.AreEqual(Messages.Done, view.StatusMessage); 
} 

Ahora, después he añadido un SynchronizationContext que rosca al presentador traté de establecer un AutoResetEvent en mi punto de vista falso para el StatusMessage, pero cuando Ejecuto la prueba SynchronizationContext.Current es nulo. Me doy cuenta de que el modelo de subprocesamiento que estoy usando en mi nuevo presentador no es perfecto, pero ¿es esta la técnica adecuada para probar el subprocesamiento múltiple? ¿Por qué mi SynchronizationContext.Current es nulo? ¿Qué debería hacer?

Presentador después de enhebrar

public class FtpPresenter : IFtpPresenter 
{ 
    ... 
    void _view_GetFilesClicked(object sender, EventArgs e) 
    { 
     _view.StatusMessage = Messages.Loading; 

     try 
     { 
      var settings = new FtpAuthenticationSettings() 
      { 
       Site = _view.FtpSite, 
       Username = _view.FtpUsername, 
       Password = _view.FtpPassword 
      }; 
      // Wrap the GetFiles in a ThreadStart 
      var syncContext = SynchronizationContext.Current; 
      new Thread(new ThreadStart(delegate 
      { 
       var files = _ftpService.GetFiles(settings); 
       syncContext.Send(delegate 
       { 
        _view.FilesDataSource = files; 
        _view.StatusMessage = Messages.Done; 
       }, null); 
      })).Start(); 
     } 
     catch (Exception ex) 
     { 
      _view.StatusMessage = ex.Message; 
     } 
    } 
    ... 
} 

prueba después de enhebrar

[TestMethod] 
public void Can_Get_Files() 
{ 
    var view = new FakeFtpView(); 
    var presenter = new FtpPresenter(view, new FakeFtpService(), new FakeFileValidator()); 

    view.GetFiles(); 
    view.GetFilesWait.WaitOne(); 
    Assert.AreEqual(Messages.Done, view.StatusMessage); 
} 

Ver falso

public class FakeFtpView : IFtpView 
{ 
    ... 
    public AutoResetEvent GetFilesWait = new AutoResetEvent(false); 
    public event EventHandler GetFilesClicked = delegate { }; 
    public void GetFiles() 
    { 
     GetFilesClicked(this, EventArgs.Empty); 
    } 
    ... 
    private List<string> _statusHistory = new List<string>(); 
    public List<string> StatusMessageHistory 
    { 
     get { return _statusHistory; } 
    } 
    public string StatusMessage 
    { 
     get 
     { 
      return _statusHistory.LastOrDefault(); 
     } 
     set 
     { 
      _statusHistory.Add(value); 
      if (value != Messages.Loading) 
       GetFilesWait.Set(); 
     } 
    } 
    ... 
} 
+0

¡Buena pregunta! ¡Estoy tratando de resolver un problema similar! –

Respuesta

3

me he encontrado con problemas similares con ASP.NET MVC donde es el HttpContext eso falta Una cosa que puede hacer es proporcionar un constructor alternativo que le permita inyectar un SynchronizationContext simulado o exponer un setter público que haga lo mismo. Si no puede cambiar el SynchronizationContext internamente, establezca una propiedad que establezca en SynchronizationContext.Current en el constructor predeterminado y use esa propiedad en todo su código. En su constructor alternativo, puede asignar el contexto de simulación a la propiedad, o puede asignarlo directamente si le otorga un setter público.

public class FtpPresenter: IFtpPresenter { public SynchronizationContext CurrentContext {get; conjunto; }

public FtpPresenter() : this(null) { } 

    public FtpPresenter(SynchronizationContext context) 
    { 
     this.CurrentContext = context ?? SynchronizationContext.Current; 
    } 

    void _view_GetFilesClicked(object sender, EventArgs e) 
    { 
    .... 
    new Thread(new ThreadStart(delegate 
     { 
      var files = _ftpService.GetFiles(settings); 
      this.CurrentContext.Send(delegate 
      { 
       _view.FilesDataSource = files; 
       _view.StatusMessage = Messages.Done; 
      }, null); 
     })).Start(); 

    ... 
    } 

Otra observación que me gustaría hacer es que probablemente tendría su presentador dependerá de una interfaz para la clase Thread y no en Hilo directamente. No creo que las pruebas unitarias deban crear nuevos hilos, sino que interactúen con una clase falsa que solo asegure que se invoquen los métodos adecuados para crear subprocesos. Podrías inyectar esa dependencia también.

Si SynchronizationContext.Current no existe cuando se llama al constructor, es posible que deba mover la lógica de asignación a Current en el getter y realizar la carga lenta.

+0

¿Qué usaría en mi prueba para reemplazar el SynchronizationContext.Current? ¿Tienes alguna muestra de código? – bendewey

+0

No sé si Synchronizationcontext se ajusta a una interfaz. Si es así, puede simular una clase que utiliza la misma interfaz e inyectar la interfaz en su lugar. De lo contrario, puede definir un contenedor alrededor de SynchronizationContext que implemente (su propia) interfaz y simule la clase contenedora. – tvanfosson

+0

Mire la fuente de HttpContextWrapper (que es con lo que estoy familiarizado) en www.codeplex.com/aspnet en el árbol de fuentes de MVC para obtener ideas sobre cómo hacer esto. – tvanfosson

1

Tiene mucha aplicación lógica en su presentador. Ocultaría contextos e hilos dentro de un modelo concreto y probaré la funcionalidad solo.