microsoft / codecoverage

MIT License
73 stars 12 forks source link

Inconsistent coverage report #113

Closed wgebczyk closed 3 months ago

wgebczyk commented 3 months ago

Hello,

I've spotted inconsistent coverage report. It seems that code used by JsonSerializer "does not count as used".

Class1.cs:
public record SomeSettings(
    string Name,
    IReadOnlyList<OtherThing> Other
);
public record OtherThing(
    string Value
);

...

using System.Text.Json;

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        JsonSerializer.Deserialize<SomeSettings>(@"{ ""Name"": ""aaa"" }");
    }
}

Executed via dotnet-coverage collect 'dotnet test' -f xml -o coverage.xml reports:

(heavily cut to reduce space)
<results>
  <modules>
    <module block_coverage="16.67" line_coverage="26.67" blocks_covered="2" blocks_not_covered="10" lines_covered="4" lines_partially_covered="0" lines_not_covered="11">
      <functions>
        <function name="SomeSettings(string, System.Collections.Generic.IReadOnlyList&lt;OtherThing&gt;)" type_name="SomeSettings">
          <ranges>
            <range start_line="1" end_line="4" start_column="15" end_column="2" covered="yes" />
          </ranges>
        </function>
        <function name="get_Name()" type_name="SomeSettings" blocks_not_covered="1">
          <ranges>
            <range start_line="2" end_line="2" covered="no" />
          </ranges>
        </function>
        <function name="set_Name(string)" type_name="SomeSettings" blocks_not_covered="1">
          <ranges>
            <range start_line="2" end_line="2" covered="no" />
          </ranges>
        </function>
        <function name="get_Other()" type_name="SomeSettings" blocks_not_covered="1">
          <ranges>
            <range source_id="0" start_line="3" end_line="3" start_column="5" end_column="36" covered="no" />
          </ranges>
        </function>
        <function name="set_Other(System.Collections.Generic.IReadOnlyList&lt;OtherThing&gt;)" type_name="SomeSettings" blocks_not_covered="1">
          <ranges>
            <range source_id="0" start_line="3" end_line="3" start_column="5" end_column="36" covered="no" />
          </ranges>
        </function>
      </functions>
    </module>
  </modules>
</results>

Expected: In this example deserialization will touch at least set_Name (indirectly Name property), hence this "should be covered".

PS: This report is not about if test makes sense, if testing POCO makes sense, using dotnet-coverage makes sense etc.

jakubch1 commented 3 months ago

Hello @wgebczyk, in your scenario constructor is invoked to create instance of SomeSettings and constructor is not using setter. To get setter covered you need to use syntax with. Check: https://github.com/microsoft/codecoverage/issues/102#issuecomment-1959564840

If you don't want set_Name to be in coverage report you can also exclude it using configuration. You can also use attribute [ExcludeFromCodeCoverage] on top of record to exclude it but this will also exclude constructor.

wgebczyk commented 3 months ago

Thank you for your answer. After second though, you are right, this ctor wont use property, but directly set backing field.