代码之家  ›  专栏  ›  技术社区  ›  AngryHacker

如何对这个库进行单元测试?

  •  2
  • AngryHacker  · 技术社区  · 14 年前

    public class Foo
    {
        public delegate void CompletedEventHandler(object sender, EventArgs e);
        public event CompletedEventHandler Completed;
    
        public void LongRunningTask()
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            bw.RunWorkerAsync();
        }
    
        void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(5000);
        }
    
        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (Completed != null)
                Completed(this, EventArgs.Empty);
        }
    }
    

    调用此库的代码如下所示:

    private void button1_Click(object sender, EventArgs e)
    {
        Foo b = new Foo();
        b.Completed += new Foo.CompletedEventHandler(b_Completed);
        b.LongRunningTask();
    
        Debug.WriteLine("It's all done");    
    }
    
    void b_Completed(object sender, EventArgs e)
    {
        // do stuff
    }
    

    如果对.LongRunningTask的调用在事件中返回数据,如何对其进行单元测试?

    4 回复  |  直到 14 年前
        1
  •  2
  •   treze    14 年前

    我不确定我是否做对了。是否要检查外部库是否触发事件?或者你想检查一下你是否做了什么特别是在事件被触发的时候?

    如果是后者,我会用一个模拟来做。但问题是,你的代码似乎很难测试,因为你在用户界面上做逻辑工作。试着写一个“被动的”视图,让一个演示者来做魔术。例如,通过使用“模型-视图-演示者”模式 http://msdn.microsoft.com/en-us/magazine/cc188690.aspx

    然后整件事就会变成这样。

    public class Model : IModel
    {
        public event EventHandler<SampleEventArgs> Completed;
    
        public void LongRunningTask()
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += this.bw_DoWork;
            bw.RunWorkerCompleted += this.bw_RunWorkerCompleted;
            bw.RunWorkerAsync();
    
        }
    
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (this.Completed != null)
            {
                this.Completed(this, new SampleEventArgs {Data = "Test"});
            }
        }
    
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            System.Threading.Thread.Sleep(5000);
        }
    }
    

    景色

        public Form1()
        {
            InitializeComponent();
        }
    
        public event EventHandler Button1Clicked;
    
        public void Update(string data)
        {
            this.label1.Text = data;
        }
    
        private void Button1Click(object sender, EventArgs e)
        {
            if (this.Button1Clicked != null)
            {
                this.Button1Clicked(this, EventArgs.Empty);
            }
        }
    

    演示者

    public class Presenter
    {
        private readonly IForm1 form1;
        private readonly IModel model;
    
        public Presenter(IForm1 form1, IModel model)
        {
            this.form1 = form1;
            this.model = model;
    
            this.form1.Button1Clicked += this.Form1Button1Clicked;
            this.model.Completed += this.ModelCompleted;
        }
    
        private void ModelCompleted(object sender, SampleEventArgs e)
        {
            this.form1.Update(e.Data);
        }
    
        private void Form1Button1Clicked(object sender, EventArgs e)
        {
            this.model.LongRunningTask();
        }
    }
    

    var form = new Form1();
    var model = new Model();
    var presenter = new Presenter(form, model);
    Application.Run(form);
    

    然后你可以很容易的在单元测试中测试演示者。gui中的部分现在已经小到不需要测试了。

    可能的测试是这样的

        [Test]
        public void Test()
        {
            var form1Mock = new Mock<IForm1>();
            var modelMock = new Mock<IModel>();
    
            var presenter = new Presenter(form1Mock.Object, modelMock.Object);
    
            modelMock.Setup(m => m.LongRunningTask()).Raises(m => m.Completed += null, new SampleEventArgs() { Data = "Some Data" });
    
            form1Mock.Raise(f => f.Button1Clicked += null, EventArgs.Empty);
    
            form1Mock.Verify(f => f.Update("Some Data"));
        } 
    
        2
  •  2
  •   Jon Skeet    14 年前

    嗯,我相信 BackgroundWorker 使用当前 SynchronizationContext . 你可以实现你自己的子类 行绪 取决于 在另一个线程中运行)并调用 SetSynchronizationContext

    您需要在测试中订阅事件,然后检查是否调用了处理程序。(Lambda表达式对此很好。)

    例如,假设您有一个 它允许您仅在需要时运行所有工作,并在完成时告诉您,您的测试可能:

    • 设置同步上下文
    • 创建组件
    • 呼叫 LongRunningTask()
    • 确认尚未设置局部变量
    • 使同步上下文完成其所有工作。。。等待它完成(超时)
    • 确认本地变量现在已设置

    无可否认,这都有点恶心。如果你 只是测试它正在做的工作,同时,那会容易得多。

        3
  •  1
  •   aqwert    14 年前

    您可以创建一个扩展方法来帮助将其转换为同步调用。您可以做一些调整,比如使它更通用,并传入超时变量,但至少它将使单元测试更易于编写。

    static class FooExtensions
    {
        public static SomeData WaitOn(this Foo foo, Action<Foo> action)
        {
            SomeData result = null;
            var wait = new AutoResetEvent(false);
    
            foo.Completed += (s, e) =>
            {
                result = e.Data; // I assume this is how you get the data?
                wait.Set();
            };
    
            action(foo);
            if (!wait.WaitOne(5000)) // or whatever would be a good timeout
            {
                throw new TimeoutException();
            }
            return result;
        }
    }
    
    public void TestMethod()
    {
        var foo = new Foo();
        SomeData data = foo.WaitOn(f => f.LongRunningTask());
    }
    
        4
  •  1
  •   trendl    14 年前

    为了测试异步代码,我使用了类似的助手:

    public class AsyncTestHelper
    {
        public delegate bool TestDelegate();
    
        public static bool AssertOrTimeout(TestDelegate predicate, TimeSpan timeout)
        {
            var start = DateTime.Now;
            var now = DateTime.Now;
            bool result = false;
            while (!result && (now - start) <= timeout)
            {
                Thread.Sleep(50);
                now = DateTime.Now;
                result = predicate.Invoke();
            }
            return result;
        }
    }
    

    在测试方法中,然后调用如下内容:

    Assert.IsTrue(AsyncTestHelper.AssertOrTimeout(() => changeThisVarInCodeRegisteredToCompletedEvent, TimeSpan.FromMilliseconds(500)));