问题
I found a scenario with SonarQube 5.3 where different values are being reported in code coverage from Visual Studio code coverage analysis.
Here is a small reproduction, using the MSTest framework.
I'm not able to determine if there is something wrong in what we are doing or if one of the applications is wrong.
The object being tested
[Serializable]
public class Document : IEquatable<Document>
{
public long Id { get; set; }
public string Name { get; set; }
public long DocumentHandle { get; set; }
public long BatchId { get; set; }
public string BatchName { get; set; }
public string RepositoryName { get; set; }
public long DocumentTypeId { get; set; }
public string DocumentTypeName { get; set; }
public int SequenceNumber { get; set; }
public string LoanNumber { get; set; }
public bool IsJunked { get; set; }
public DateTime ArrivalDate { get; set; }
public bool Equals(Document other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (Id != other.Id)
{
return false;
}
if (!string.Equals(Name, other.Name))
{
return false;
}
if (DocumentHandle != other.DocumentHandle)
{
return false;
}
if (BatchId != other.BatchId)
{
return false;
}
if (!string.Equals(BatchName, other.BatchName))
{
return false;
}
if (!string.Equals(RepositoryName, other.RepositoryName))
{
return false;
}
if (DocumentTypeId != other.DocumentTypeId)
{
return false;
}
if (!string.Equals(DocumentTypeName, other.DocumentTypeName))
{
return false;
}
if (SequenceNumber != other.SequenceNumber)
{
return false;
}
if (!string.Equals(LoanNumber, other.LoanNumber))
{
return false;
}
if (IsJunked != other.IsJunked)
{
return false;
}
if (ArrivalDate != other.ArrivalDate)
{
return false;
}
return true;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return Equals((Document) obj);
}
public static bool operator == (Document document1, Document document2)
{
return ReferenceEquals(document1, null) ? ReferenceEquals(document2, null) : document1.Equals(document2);
}
public static bool operator != (Document document1, Document document2)
{
return !(document1 == document2);
}
public override int GetHashCode()
{
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
// This was done to suppress the messages about needing to override GetHashCode
// Because this class has no ReadOnly properties there is no way to provide a better hashcode
return base.GetHashCode();
}
}
The code has the following tests:
[TestClass]
[ExcludeFromCodeCoverage]
public class DocumentTests
{
private Document defaultDocument;
private Document alteredDocument;
[TestInitialize]
public void Setup()
{
defaultDocument = new Document
{
Id = 1,
Name = "Growlithe",
DocumentHandle = 2,
BatchId = 3,
BatchName = "Vulpix",
RepositoryName = "Pancham",
DocumentTypeId = 4,
DocumentTypeName = "Skrelp",
SequenceNumber = 5,
LoanNumber = "Zorua",
IsJunked = true,
ArrivalDate = new DateTime(1, 1, 1)
};
alteredDocument = new Document
{
Id = 1,
Name = "Growlithe",
DocumentHandle = 2,
BatchId = 3,
BatchName = "Vulpix",
RepositoryName = "Pancham",
DocumentTypeId = 4,
DocumentTypeName = "Skrelp",
SequenceNumber = 5,
LoanNumber = "Zorua",
IsJunked = true,
ArrivalDate = new DateTime(1, 1, 1)
};
}
[TestMethod]
public void ToStringMethod_DocumentPOCO_ConvertObjectToString()
{
// Arrange
var expectedStringDocument = "Document" + Environment.NewLine +
"\tId: 101" + Environment.NewLine +
"\tName: TestName" + Environment.NewLine +
"\tDocumentHandle: 5000000" + Environment.NewLine +
"\tBatchId: 500000000" + Environment.NewLine +
"\tBatchName: TestBatchName" + Environment.NewLine +
"\tRepositoryName: TestRepositoryName" + Environment.NewLine +
"\tDocumentTypeId: 5000000" + Environment.NewLine +
"\tDocumentTypeName: TestDocumentTypeName" + Environment.NewLine +
"\tSequenceNumber: 101" + Environment.NewLine +
"\tLoanNumber: TestLoanNumber" + Environment.NewLine +
"\tIsJunked: False" + Environment.NewLine +
"\tArrivalDate: " + DateTime.Now + Environment.NewLine;
alteredDocument = new Document
{
Id = 101,
Name = "TestName",
DocumentHandle = 5000000,
BatchId = 500000000,
BatchName = "TestBatchName",
RepositoryName = "TestRepositoryName",
DocumentTypeId = 5000000,
DocumentTypeName = "TestDocumentTypeName",
SequenceNumber = 101,
LoanNumber = "TestLoanNumber",
IsJunked = false,
ArrivalDate = DateTime.Now
};
// Act
var processedDocumentObj = StringUtility.StringUtility.ConvertToString(alteredDocument);
// Assert
Assert.IsTrue(processedDocumentObj.Equals(expectedStringDocument));
}
[TestMethod]
public void EqualsReturnsTrueForEquivalentDocument()
{
Assert.IsTrue(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForNullDocument()
{
alteredDocument = null;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentId()
{
alteredDocument.Id = 9;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentName()
{
alteredDocument.Name = "Arcanine";
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentDocumentHandle()
{
alteredDocument.DocumentHandle = 9;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentBatchId()
{
alteredDocument.BatchId = 9;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentBatchName()
{
alteredDocument.BatchName = "Ninetails";
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentRepositoryName()
{
alteredDocument.RepositoryName = "Pangoro";
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentDocumentTypeId()
{
alteredDocument.DocumentTypeId = 9;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentDocumentTypeName()
{
alteredDocument.DocumentTypeName = "Dragalge";
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentSequenceNumber()
{
alteredDocument.SequenceNumber = 9;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentLoanNumber()
{
alteredDocument.LoanNumber = "Zoroark";
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentIsJunked()
{
alteredDocument.IsJunked = false;
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsReturnsFalseForDifferentArrivalDate()
{
alteredDocument.ArrivalDate = new DateTime(2, 2, 2);
Assert.IsFalse(defaultDocument.Equals(alteredDocument));
}
[TestMethod]
public void EqualsOperatorWorksForNonNullValues()
{
Assert.IsTrue(defaultDocument == alteredDocument);
}
[TestMethod]
public void NotEqualsOperatorWorksForNonNullValues()
{
alteredDocument = null;
Assert.IsTrue(defaultDocument != alteredDocument);
}
[TestMethod]
public void EqualsOperatorReturnsFalseForNullDotNonNull()
{
alteredDocument = null;
Assert.IsFalse(alteredDocument == defaultDocument);
}
[TestMethod]
public void EqualsOperatorReturnsFalseForNonNullDotNull()
{
alteredDocument = null;
Assert.IsFalse(defaultDocument == alteredDocument);
}
[TestMethod]
public void EqualsOperatorReturnsTrueForNullDotNull()
{
alteredDocument = null;
defaultDocument = null;
Assert.IsTrue(defaultDocument == alteredDocument);
}
}
Visual Studio shows the percentage as: 90.10%
SonarQube shows the percentage as: 40.00%
Sonar doesn't appear to consider the early return statements after the
if (ReferenceEquals(other, null))
{
return false;
}
of the method: public bool Equals(Document other)
I have debugged the tests to validate the lines are hit.
回答1:
It may be a difference between line/branch coverage: What is the difference between code coverage and line coverage in sonar
... or white spacing/line wrapping.
You can find the formula for SonarQube's metric description page here: http://docs.sonarqube.org/display/SONAR/Metric+definitions#Metricdefinitions-Tests
coverage = (CT + CF + LC)/(2*B + EL)
where
CT - branches that evaluated to "true" at least once CF - branches that evaluated to "false" at least once LC - lines covered (lines_to_cover - uncovered_lines)
B - total number of branches (2*B = conditions_to_cover) EL - total number of executable lines (lines_to_cover)
来源:https://stackoverflow.com/questions/35876546/code-coverage-differences-in-sonarqube-net