关于可空类型Nullable的一些学习

本文探讨了C#中可空类型在单元测试时的覆盖率问题,通过对比不同情况下的中间语言(IL)代码,揭示了何时及为何需要对非空值和空值进行测试以达到完全覆盖。

最近的项目中单体测试时遇到了关于可空类型的一些疑惑,具体问题是这样的:

在一个方法体内部有一些对可空类型的赋值操作,当对这个方法进行单体测试时,有时仅仅进行非空值的赋值测试,即可是覆盖率达到100%,但有时却必须进行非空值和空值的测试之后才能使覆盖率达到100%。到底为什么会出现这个问题呢?是不是编译器自动给一些方法加上了一些if等的条件分歧操作呢?

 

让我们来看个简单的例子吧。

 

在这里有两个方法,分别接受decimal的非空和可空版本来对一个非空或者可空版本来进行赋值。

    public void test1(decimal n)

    {

        decimal m;

        m = n;

    }

 

    public void test2(decimal? n)

    {

        decimal? m;

        m = n;

    }

具体的IL如下

.method public hidebysig instance void  test1(valuetype [mscorlib]System.Decimal n) cil managed

{

  // コード サイズ       4 (0x4)

  .maxstack  1

  .locals init (valuetype [mscorlib]System.Decimal V_0)

  IL_0000:  nop

  IL_0001:  ldarg.1

  IL_0002:  stloc.0

  IL_0003:  ret

} // end of method a::test1

 

.method public hidebysig instance void  test2(valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> n) cil managed

{

  // コード サイズ       4 (0x4)

  .maxstack  1

  .locals init (valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> V_0)

  IL_0000:  nop

  IL_0001:  ldarg.1

  IL_0002:  stloc.0

  IL_0003:  ret

} // end of method a::test2

 

IL可见这两个方法都是初始化内部变量,然后将参数加加入堆栈(IL_001),然后将堆栈顶端的条目存入局部变量(IL_0002)。于是我大胆的推测,这时的test2的单体测试只要进行非空值或者空值的任意一个case就可以使覆盖率达到100%,同时测试的结果也验证了我的猜测的正确性。

 

那么到底什么情况下,对于可空类型的单体测试的必须进行可空值和空值的测试才能保证覆盖率呢?这时同事一句话让我又进行了下面的测试:当输入参数为非空版本时,只要进行非空值或者空值的测试就可以保证覆盖率。

 

例子如下:

    public void test3(decimal n)

    {

        decimal? m;

        m = n;

    }

IL如下:

.method public hidebysig instance void  test3(valuetype [mscorlib]System.Decimal n) cil managed

{

  // コード サイズ       11 (0xb)

  .maxstack  2

  .locals init (valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> V_0)

  IL_0000:  nop

  IL_0001:  ldloca.s   V_0

  IL_0003:  ldarg.1

  IL_0004:  call       instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>::.ctor(!0)

  IL_0009:  nop

  IL_000a:  ret

} // end of method a::test3

 

对比test2,我们发现test3发生了很大的变化。

但是单体测试仍然只要进行非空值或者空值的任意一个case就可以使覆盖率达到100%

 

那到底为何在项目中出现了一些必须进行非空值空值的测试才可能满足覆盖率的问题呢,重新对检查了项目和自己的测试例子的区别,发现实际的项目还存在类型的隐式转换,于是又进行了下面的测试用例:

 

    public void test4(int n)

    {

        decimal? m;

 

        m = n;

    }

 

 

    public void test5(int? n)

    {

        decimal? m;

 

        m = n;

    }

再来重新看一下IL

.method public hidebysig instance void  test4(int32 n) cil managed

{

  // コード サイズ       16 (0x10)

  .maxstack  2

  .locals init (valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> V_0)

  IL_0000:  nop

  IL_0001:  ldloca.s   V_0

  IL_0003:  ldarg.1

  IL_0004:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int32)

  IL_0009:  call       instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>::.ctor(!0)

  IL_000e:  nop

  IL_000f:  ret

} // end of method a::test4

 

.method public hidebysig instance void  test5(valuetype [mscorlib]System.Nullable`1<int32> n) cil managed

{

  // コード サイズ       42 (0x2a)

  .maxstack  3

  .locals init (valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> V_0,

           valuetype [mscorlib]System.Nullable`1<int32> V_1,

           valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> V_2)

  IL_0000:  nop

  IL_0001:  ldarg.1

  IL_0002:  stloc.1

  IL_0003:  ldloca.s   V_1

  IL_0005:  call       instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()

  IL_000a:  brtrue.s   IL_0017

  IL_000c:  ldloca.s   V_2

  IL_000e:  initobj    valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>

  IL_0014:  ldloc.2

  IL_0015:  br.s       IL_0028

  IL_0017:  ldloca.s   V_1

  IL_0019:  call       instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()

  IL_001e:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int32)

  IL_0023:  newobj     instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>::.ctor(!0)

  IL_0028:  stloc.0

  IL_0029:  ret

} // end of method a::test5

 

这次让我眼睛一亮的是,在test5中出现了条件判断,这在我们的C#中是没有的,为什么会出现这判断呢。仔细分析IL,发现这个地方是就引出了Nullable<T>的一些特性。

详细地对test5进行分析,再IL中存在3个局部变量:两个Nullable< Decimal >和一个Nullable<int32>,这与之前的test1-test4是明显的不同的,这充分说明编译器为我们追加了两个局部变量的定义,

继续往下看,000a判断参数是否为空,如果不为空,则跳转到0017将局部变量V_1入栈。如果为空,则继续执行000c

到这里我们就很明显的可以得到结论test5,必须进行非空值空值的测试才可能满足覆盖率100%

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值