单元测试,验证未调用方法是否是一个好主意

时间:2018-10-16 12:24:21

标签: unit-testing testing tdd

假设您具有以下测试方法:

public partial class BaseButtonTemplate : ContentView
{
    public static readonly BindableProperty ButtonHeightRequestProperty = BindableProperty.Create(nameof(ButtonHeightRequest), typeof(string), typeof(BaseButtonTemplate), "");
    public static readonly BindableProperty EnabledProperty = BindableProperty.Create(nameof(Enabled), typeof(bool), typeof(BaseButtonTemplate), default(bool));
    public static readonly BindableProperty FrameBackgroundColorProperty = BindableProperty.Create(nameof(FrameBackgroundColor), typeof(Color), typeof(BaseButtonTemplate), Color.FromHex("FFFFFF"));
    public static readonly BindableProperty FrameBorderColorProperty = BindableProperty.Create(nameof(FrameBorderColor), typeof(Color), typeof(BaseButtonTemplate), Color.FromHex("FFFFFF"));
    public static readonly BindableProperty LabelTextColorProperty = BindableProperty.Create(nameof(LabelTextColor), typeof(Color), typeof(BaseButtonTemplate), Color.FromHex("FFFFFF"));
    public static readonly BindableProperty ParamProperty = BindableProperty.Create(nameof(Param), typeof(string), typeof(BaseButtonTemplate), default(string));
    public static readonly BindableProperty TapCommandParamProperty = BindableProperty.Create(nameof(TapCommandParam), typeof(object), typeof(BaseButtonTemplate), default(object));
    public static readonly BindableProperty TapCommandProperty = BindableProperty.Create( "TapCommand", typeof(Command), typeof(BaseButtonTemplate), defaultBindingMode: BindingMode.TwoWay, defaultValue: default(Command));
    public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(BaseButtonTemplate), default(string));

    public Color FrameBackgroundColor { get { return (Color)GetValue(FrameBackgroundColorProperty); } set { SetValue(FrameBackgroundColorProperty, value); } }
    public Color FrameBorderColor { get { return (Color)GetValue(FrameBorderColorProperty); } set { SetValue(FrameBorderColorProperty, value); } }
    public Color LabelTextColor { get { return (Color)GetValue(LabelTextColorProperty); } set { SetValue(LabelTextColorProperty, value); } }
    public Command TapCommand { get { return (Command)GetValue(TapCommandProperty); } set { SetValue(TapCommandProperty, value); } }
    public bool Enabled { get; set; }
    public object TapCommandParam { get { return (object)GetValue(TapCommandParamProperty); } set { SetValue(TapCommandParamProperty, value); } }
    public string ButtonHeightRequest { get { return (string)GetValue(ButtonHeightRequestProperty); } set { SetValue(ButtonHeightRequestProperty, value); } }
    public string Param { get { return (string)GetValue(ParamProperty); } set { SetValue(ParamProperty, value); } }
    public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }

}

对这种功能进行单元测试的最佳方法是什么?如果您编写2个测试  断言,当myBool为true时断言repositoryA,而当myBool为false时调用repositoryB,那么对该函数的以下更改仍将使测试通过,但有可能破坏应用程序的功能:

library(tidyverse)
xy <- data.frame(xvar = 1:10, yvar = 1:10)


plotfunc2 <- function(data, x, y){
  x <- enquo(x)
  y <- enquo(y)
  rng <- data %>% summarise(r1 = range(!!x, na.rm = TRUE)[1],
                             r2 = range(!!x, na.rm = TRUE)[2]) 
  new_data <- data %>% mutate(rescale = (!!x - rng$r1)/(rng$r2 - rng$r1)) 
  ggplot(data, aes(x = !!x, y = !!y)) +
      geom_line()
}
plotfunc2(xy, xvar, yvar)

另一方面,如果您断言myBool为true时,则调用repositoryA且未调用repositoryB,这使您更有信心,对函数的任何更改都不会引入错误,但是您可以进行测试取决于实施细节。最好的方法是什么?

如果使用TDD,您会写什么测试才能达到所需的功能?

2 个答案:

答案 0 :(得分:0)

回答您的问题(也请阅读我的评论,从更广泛的意义上讲可能是有价值的),我会写类似的东西:(使用Java模拟库JMock)

// with myBool == true

context.checking(new Expectations(){{
    oneOf(repositoryA).save(object);
    never(repositoryB);
}});

underTest.foo(object, true);




// with myBool == false

context.checking(new Expectations(){{
    never(repositoryA);
    oneOf(repositoryB).save(object);
}});

underTest.foo(object, false);

答案 1 :(得分:0)

  

对这种功能进行单元测试的最佳方法是什么?

从重新设计开始吗?

首先开发测试的部分要点是声称可测试的接口也“更好”;更容易食用,更易于维护。

因此,您(正确地)提出的有关测试此方法副作用的观点是一种“设计异味”-暗示此代码设计可能不是用例所需要的。

两种可能性:

一个是代码试图告诉您您有遥测要求;您应该能够查询被测系统,并了解到每个存储库中已经保存了多少个对象,或者最后一个保存到每个存储库中的对象是什么,或者类似的内容。

然后,您可以利用遥测技术编写测试

Given:
    telemetry reports that repository B has stored 7 objects
    and myBool is true
When:
    foo()
Then:
    telemetry reports that repository B has stored 7 objects

这基本上将问题分为两部分:一组测试,以确保遥测准确报告存储库保存对象的次数,然后进行foo()的测试以假定遥测有效。

第二种选择:该测试试图告诉您您希望能够评估程序中正在发生的副作用。因此,请使这些效果成为设计中的头等公民,并编写检查效果的测试。

List<Effect> foo () {
    if (myBool) {
        return List.of(SaveInRepositoryA);
    } else {
        return List.of(SaveInRepositoryB);
    }
}

现在,您的断言更加容易-您只需确保列表中包含正确数量的元素以及正确的元素即可。

重要说明:这些设计正在做的事情是在您的逻辑和副作用之间形成一个接缝。想象一个边界,在边界的一侧是易于测试的复杂逻辑,因为这是对内存中数据的全部处理;边界的另一端是难以测试的代码,但是它是如此简单直接,显然没有任何错误。