Ruthlessly Helpful
Stephen Ritchie's offerings of ruthlessly helpful software engineering practices.
Category Archives: Code Analysis
Boundary Analysis
October 29, 2020
Posted by on For every method-under-test there is a set of valid preconditions and arguments. It is the domain of all possible values that allows the method to work properly. That domain defines the method’s boundaries. Boundary testing requires analysis to determine the valid preconditions and the valid arguments. Once these are established, you can develop tests to verify that the method guards against invalid preconditions and arguments.
Boundary-value analysis is about finding the limits of acceptable values, which includes looking at the following:
- All invalid values
- Maximum values
- Minimum values
- Values just on a boundary
- Values just within a boundary
- Values just outside a boundary
- Values that behave uniquely, such as zero or one
An example of a situational case for dates is a deadline or time window. You could imagine that for a student loan origination system, a loan disbursement must occur no earlier than 30 days before or no later than 60 days after the first day of the semester.
Another situational case might be a restriction on age, dollar amount, or interest rate. There are also rounding-behavior limits, like two-digits for dollar amounts and six-digits for interest rates. There are also physical limits to things like weight and height and age. Both zero and one behave uniquely in certain mathematical expressions. Time zone, language and culture, and other test conditions could be relevant. Analyzing all these limits helps to identify boundaries used in test code.
Note: Dealing with date arithmetic can be tricky. Boundary analysis and good test code makes sure that the date and time logic is correct.
Invalid Arguments
When the test code calls a method-under-test with an invalid argument, the method should throw an argument exception. This is the intended behavior, but to verify it requires a negative test. A negative test is test code that passes if the method-under-test responds negatively; in this case, throwing an argument exception.
The test code shown here fails the test because ComputePayment is provided an invalid termInMonths of zero. This is test code that’s not expecting an exception.
[TestCase(7499, 1.79, 0, 72.16)]
public void ComputePayment_WithProvidedLoanData_ExpectProperMonthlyPayment(
decimal principal,
decimal annualPercentageRate,
int termInMonths,
decimal expectedPaymentPerPeriod)
{
// Arrange
var loan =
new Loan
{
Principal = principal,
AnnualPercentageRate = annualPercentageRate,
};
// Act
var actual = loan.ComputePayment(termInMonths);
// Assert
Assert.AreEqual(expectedPaymentPerPeriod, actual);
}
The result of the failing test is shown, it’s output from Unexpected Exception.
LoanTests.ComputePayment_WithProvidedLoanData_ExpectInvalidArgumentException : Failed System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values. Parameter name: termInPeriods at Tests.Unit.Lender.Slos.Model.LoanTests.ComputePayment_WithProvidedLoanData_ExpectInvalidArgu mentException(Decimal principal, Decimal annualPercentageRate, Int32 termInMonths, Decimal expectedPaymentPerPeriod) in LoanTests.cs: line 25
The challenge is to pass the test when the exception is thrown. Also, the test code should verify that the exception type is InvalidArgumentException
. This requires the method to somehow catch the exception, evaluate it, and determine if the exception is expected.
In NUnit this can be accomplished using either an attribute or a test delegate. In the case of a test delegate, the test method can use a lambda expression to define the action step to perform. The lambda is assigned to a TestDelegate variable within the Act section. In the Assert section, an assertion statement verifies that the proper exception is thrown when the test delegate is invoked.
The invalid values for the termInMonths argument are found by inspecting the ComputePayment method’s code, reviewing the requirements, and performing boundary analysis. The following invalid values are discovered:
- A term of zero months
- Any negative term in months
- Any term greater than 360 months (30 years)
Below the new test is written to verify that the ComputePayment method throws an ArgumentOutOfRangeException whenever an invalid term is passed as an argument to the method. These are negative tests, with expected exceptions.
[TestCase(7499, 1.79, 0, 72.16)]
[TestCase(7499, 1.79, -1, 72.16)]
[TestCase(7499, 1.79, -2, 72.16)]
[TestCase(7499, 1.79, int.MinValue, 72.16)]
[TestCase(7499, 1.79, 361, 72.16)]
[TestCase(7499, 1.79, int.MaxValue, 72.16)]
public void ComputePayment_WithInvalidTermInMonths_ExpectArgumentOutOfRangeException(
decimal principal,
decimal annualPercentageRate,
int termInMonths,
decimal expectedPaymentPerPeriod)
{
// Arrange
var loan =
new Loan
{
Principal = principal,
AnnualPercentageRate = annualPercentageRate,
};
// Act
TestDelegate act = () => loan.ComputePayment(termInMonths);
// Assert
Assert.Throws<ArgumentOutOfRangeException>(act);
}
Invalid Preconditions
Every object is in some arranged state at the time a method of that object is invoked. The state may be valid or it may be invalid. Whether explicit or implicit, all methods have expected preconditions. Since the method’s preconditions are not spelled out, one goal of good test code is to test those assumptions as a way of revealing the implicit expectations and turning them into explicit preconditions.
For example, before calculating a payment amount, let’s say the principal must be at least $1,000 and less than $185,000. Without knowing the code, these limits are hidden preconditions of the ComputePayment
method. Test code can make them explicit by arranging the classUnderTest
with unacceptable values and calling the ComputePayment
method. The test code asserts that an expected exception is thrown when the method’s preconditions are violated. If the exception is not thrown, the test fails.
This code sample is testing invalid preconditions.
[TestCase(0, 1.79, 360, 72.16)]
[TestCase(997, 1.79, 360, 72.16)]
[TestCase(999.99, 1.79, 360, 72.16)]
[TestCase(185000, 1.79, 360, 72.16)]
[TestCase(185021, 1.79, 360, 72.16)]
public void ComputePayment_WithInvalidPrincipal_ExpectInvalidOperationException(
decimal principal,
decimal annualPercentageRate,
int termInMonths,
decimal expectedPaymentPerPeriod)
{
// Arrange
var classUnderTest =
new Application(null, null, null)
{
Principal = principal,
AnnualPercentageRate = annualPercentageRate,
};
// Act
TestDelegate act = () => classUnderTest.ComputePayment(termInMonths);
// Assert
Assert.Throws<InvalidOperationException>(act);
}
Implicit preconditions should be tested and defined by a combination of exploratory testing and inspection of the code-under-test, whenever possible. Test the boundaries by arranging the class-under-test in improbable scenarios, such as negative principal amounts or interest rates.
Tip: Testing preconditions and invalid arguments prompts a lot of questions. What is the principal limit? Is it $18,500 or $185,000? Does it change from year to year?
More on boundary-value analysis can be found at Wikipedia https://en.wikipedia.org/wiki/Boundary-value_analysis
Thank You Upstate New York Users Groups
December 28, 2012
Posted by on In November I traveled to Upstate New York to present at four .NET Users Group. Here’s the overview:
- The first stop was in Albany on Monday, Nov. 12th to present at the Tech Valley Users Group (TVUG) meeting.
- On Tuesday night I was in Syracuse presenting at the Central New York .NET Developer Group meeting.
- On Wednesday night I was in Rochester presenting at the Visual Developers of Upstate New York meeting.
- Finally, on Thursday night I was in Buffalo presenting at the Microsoft Developers in Western New York meeting.
Many Belated Thank Yous
I realize it is belated, but I’d like to extend a very big and heartfelt thank you to the organizers of these users groups for putting together a great series of meetings.
Thank you to Stephanie Carino from Apress for connecting me with the organizers. I really appreciate all the help with all the public relations, the swag, the promotion codes, the raffle copies of my book, and for the tweets and re-tweets.
Slides and Code Samples
My presentations are available on SlideShare under my RuthlessHelp account, but if you are looking for something specific then here are the four presentations:
- An Overview of .NET Best Practices
- Overcoming the Obstacles, Pitfalls, and Dangers of Unit Testing
- Advanced Code Analysis with .NET
- An Overview of .NET Best Practices
All the code samples can be found on GitHub under my RuthlessHelp account: https://github.com/ruthlesshelp/Presentations
Please Rate Me
If you attended one of these presentations, please rate me at SpeakerRate:
- Rate: An Overview of .NET Best Practices (Albany, 12-Nov)
- Rate: Overcoming the Obstacles, Pitfalls, and Dangers of Unit Testing
- Rate: Advanced Code Analysis with .NET
- Rate: An Overview of .NET Best Practices (Buffalo, 15-Nov)
You can also rate me at INETA: http://ineta.org/Speakers/SearchCommunitySpeakers.aspx?SpeakerId=b7b92f6b-ac28-413f-9baf-9764ff95be79
Thank You DC Alt.Net 2012.7
July 11, 2012
Posted by on Another great showing for the DC Alt.Net meetup last night. I hope everyone enjoyed my presentation on code analysis in .NET. There were a lot of great questions and good conversation. I really appreciate the audience participation.
Code Samples
Here are the code samples, available through GitHub.
https://github.com/ruthlesshelp/Presentations
Slides
Here are the slides, available through SlideShare.
Advanced Code Analysis In .NET
View more PowerPoint from Stephen Ritchie