【问题标题】:Unit Testing a getter that calls an async operation in a try catch block对在 try catch 块中调用异步操作的 getter 进行单元测试
【发布时间】:2014-09-09 18:26:31
【问题描述】:

我有以下代码:

public FuzzyPickles(IPie pieMaker)
{
    _pieMaker = pieMaker;
}  

public string PieName
{
    get
    {
        if (_pieName == null)
        {
            GetPieName();
        }
        return _pieName;
    }
}  

private async void GetPieName()
{
    string asyncPieName = string.Empty;
    try
    {
        var task = _pieMaker.GetDeliciousPieAsync();
        asyncPieName = await task;
    }
    catch (RottenFruitException e)
    {
        Debug.Write(e.Message);
    }  
    if (!string.IsNullOrEmpty(asyncPieName))
    {
        _pieName = asyncPieName;
        NotifyPropertyChanged(PieName);
    }
}

我想写一个测试(我正在使用 RhinoMocks)来验证那个 getter 中抛出的异常不会让我得到腐烂的水果馅饼:

[TestClass]
public class FuzzyTest
{
    private FuzzyPickles _pickles;
    private IPie _pieMocker;  

    [TestInitialize]
    public void Setup()
    {
        _pieMocker = MockRepository.Mock<IPie>();
        _pickles = new FuzzyPickles(_pieMocker);
    }  

    [TestMethod]
    public void PieName_WhenGettingException_ShouldHandleExceptionGracefully()
    {
        _pieMocker.Stub(x => x.GetDeliciousPieAsync()).Throws(new RottenFruitException());  
        var pieName = _pickles.PieName;
        //What should I assert here ?
    }
}

这是我真正的问题。

删除 try / catch 块不会将异常抛回 UT 运行器,因为我没有在 UT 中使用 await,因为它没有原型为异步任务并且 getter 不能是异步的,因此我不能等待或使用函数相反(或者我可以吗?)

那个 getter 绑定到 UI(即:我用它在我的 UI 中显示信息,所以我不知道什么时候 - 如果有的话 - 它会被调用),这就是为什么我要做这个丑陋的功能调用 hack,并在其中使用 NotifyPropertyChanged(这里不用担心跨线程异常,这不是重点)

我不想在 hacks 后面使用代码(Windows Phone 8.1 / Silverlight 项目)。

如果我删除 try / catch 块,我如何编写一个将失败的单元测试?我应该断言什么?

【问题讨论】:

    标签: c# .net unit-testing windows-phone-8 async-await


    【解决方案1】:

    由于属性与 async 不兼容(这是 IMO 的明智设计选择),您可以采用 Stephan Clearys blog post 中提到的 IntializeXXXAsync 模式。

    该模式的作用是让您调用初始化方法,该方法负责创建值。如果失败,您可以默认返回default(T),在您的字符串情况下,它只是null,并对其进行断言:

    public class FuzzyPickles : INotifyPropertyChanged
    {
        public FuzzyPickles(IPie pieMaker)
        {
            _pieMaker = pieMaker;
        }     
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private string _pieName;
        public string PieName
        {
            get
            {
                return _pieName;
            }
            private set 
            {
                _pieName = value;
               OnPropertyChanged()
            }
        }  
    
        public async Task InitializePieAsync()
        {
            string asyncPieName = string.Empty;
            try
            {
                PieName = await _pieMaker.GetDeliciousPieAsync();
            }
            catch (RottenFruitException e)
            {
                 Debug.Write(e.Message);
            }  
        }
    }
    

    现在你初始化FuzzyPickles,初始化并断言它不为空:

     [TestMethod]
     public async Task PieName_WhenGettingException_ShouldHandleExceptionGracefully()
     {
          _pieMocker.Stub(x => x.GetDeliciousPieAsync()).Throws(new RottenFruitException());  
    
          await  _pickles.InitializePieAsync();
          var pieName = _pickles.PieName;
    
          Assert.IsNotNull(pieName);
     }
    

    【讨论】:

    • 阅读 Stephan 的博文后,我意识到我的 getter 总是会被调用,我可以移动逻辑来获取它之外的值,从而解决了我的问题。谢谢!
    【解决方案2】:

    创建一个新方法GetPieNameAsync,它返回一个Task。使用await 可以轻松测试此方法。如果您出于某种原因还需要一个即发即弃的方法,请仅使用它来调用GetPieNameAsync()

    private async void GetPieName()
    {
        await GetPieNameAsync();
    }
    
    private async Task GetPieNameAsync()
    {
        string asyncPieName = string.Empty;
        try
        {
            var task = _pieMaker.GetDeliciousPieAsync();
            asyncPieName = await task;
        }
        catch (RottenFruitException e)
        {
            Debug.Write(e.Message);
        }  
        if (!string.IsNullOrEmpty(asyncPieName))
        {
            _pieName = asyncPieName;
            NotifyPropertyChanged(PieName);
        }
    }
    

    【讨论】:

    • 虽然您的解决方案在技术上是正确的,但我更喜欢@Yuval 解决方案,它更详细并包含一个不错的博客文章条目。我还要补充一点,您必须使用async void唯一 原因是用于 UI 事件处理程序,仅此而已。 blog.filipekberg.se/2012/09/20/… Filip Ekberg 的这篇博文很好地解释了原因。
    猜你喜欢
    • 2013-12-19
    • 2020-06-08
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 1970-01-01
    相关资源
    最近更新 更多