This article will cover basic testing concept, best practices and principles to write unit testing, how to write test data for unit testing and naming convention using Behavior Driven Development (BDD).
Software Testing
Readers of this topics - Developers, Application Architect.
What Will You Learn from this Topic
We know the difference between the Unit Testing and Integration Testing. But sometimes, we mix Unit Test with Integration Testing. But it is not expected. I will explain the principles and best practice to write unit testing and how to write the test cases using Behavior Driven Development (BDD) during white-box testing. The following concepts will be covered:
- White-Box and Black-Box
- Advantages and disadvantages of Unit Testing
- Principles to Write Unit Testing
- Best Practice to Write Unit Tests
- Writing Test Scenario
- Writing Test Case
- Writing Test Data
- Traditional Unit Testing Principle
- Naming Convention of Test Method
- Why Need Behavior Driven Development (BDD)
- No Tight-Coupling For Unit Testing
Basic Overview of the Software Testing
Benefit of Software Testing
- Reduce risk of failures when systems are transferred to production or live operation
- Documental proof that business requirements have been met
- Assurance that users are able to operate designed solution productively
- Assurance that the system works properly with existing legacy systems
Software Tester - Developer VS. QA
Developer's Point Of View
Developers can verify their implemented logics or codes according to the business requirement. They make sure that all of the logics can run smoothly. On the other hand, they confirm that if end-user passes any un-expected data, then the logics still can handle the exception as well as it can show the proper messages. This is known as White-Box Testing because they can see their internal logic or code. For example, automated unit-testing or automated integration-testing.
QA's Point Of View
Quality Assurance (QA) team can verify the software according to the business requirements. But they don't need to worry about the logic or code. They are testing the functionality as an end-user. They will input the positive data to verify the positive functionality. On the other hand, they will put the wrong data to make sure that they are getting the proper messages for the wrong input. This is known as a Black-Box Testing because they can't see the internal logic or code. They verify the external behaviors. For example, acceptance testing.
Let's Drilldown the Basic Testing Concept
Test Document Basic
- Test Case – A set of conditions and steps to determine whether a system satisfies the requirements correctly or not. This is described by test data, environment and expected result.
- Test Suite – a collection of test cases.
- Test Plan – document describing testing approach, test suites and test cases.
- Test Strategy – a way to identify test cases from the specification.
- Test Effectiveness – relative ability of test strategy to find bugs.
- Test Coverage – the percentage of testable elements that have been tested.
Test Scenarios & Test Cases
If you have to withdraw money from an ATM machine, then it is a scenario. But to withdraw money, you need to execute many test cases.
Test Scenarios - A test-scenario can have multiple test cases. So while starting testing, first prepare test scenarios, then create test cases for each scenario.
Example of the Test Scenario: Checking the functionality of the Login button.
Test-Case - A Test Case is a condition which is executed for expected output with predefined set of steps with known inputs.
Example for Test Cases:
Test-Case1
: Click on the button without entering user-name and password. Test-Case2
: Click on the button only entering user-name. Test-Case3
: Click on the button while entering wrong user-name and wrong password, etc.
All of these test-cases will have some expected, unexpected and actual result.
Term of Mistake
- Error - mistake made by developers during implementation of a software system
- Failure - incorrect behavior of a program
- Fault - incorrect code that caused a failure
- Incident - symptoms associated with a failure
- Bug - an error or fault
- Fault Directed testing – finding bugs through failure
Positive Testing and Negative Testing
Positive Testing: When Tester tested the application with valid input/data from positive point of view, then it is known as positive testing.
Negative Testing: When Tester tested the application with invalid input/data from negative point of view, then it is known as negative testing.
White Box And Black Box Testing
Depending on the developers and QA Team, we can say that we have two types of testing. Developers can view the internal logics of the Implemented Application system or development box. This box is visible to the developer and it's called white-Box. Other the hand, the QA team can't see the logical implementation and they can see the functionality of the system as an end user point of view. That's why the box is black and they test the external behaviors. It is called Black-Box Testing.
Advantages And Disadvantages of White Box Testing
Advantages
- Reveals bugs in hidden code
- Forces reason in implementation for positive, negative and exceptional cases
- Get confident and documental proof
- Improving design and code quality
Disadvantages
- Expensive compared to time and design
- Need proper knowledge about testing methodology
- Cases missed can miss out code
Advantages and Disadvantages of Black Box Testing
Advantages
- Tester and programmer are independent
- Testing from user’s point of view
- Test cases can be prepared right after specification are decided
Disadvantages
- Duplication of test cases by testers and programmer
- Only simple testing can be done as all cases can’t be build and tested
Black Box vs. White Box Testing Approach
Black-Box approach
- Field level check
- Field level validation
- User interface check
- Function level check
White Box approach
- Statement coverage
- Decision coverage
- Condition coverage
- Path coverage
Type of Testing
- Unit Testing
- Integration Testing
- Functional Testing
- System Testing
- Stress Testing
- Performance Testing
- Usability Testing
- Acceptance Testing
- Regression Testing
- Beta Testing
Most Common Testing in Many Organizations
Granularity Levels
- Unit Test - Verification of single classes
- Module Test - Testing interaction of groups of classes (package)
- Integration Test - Verification of interactions between modules/components
- Functional Test - Verification of external behaviors of modules, components and system
- System Test - Testing of the system against objectives
- Acceptance Test - Validation of application against user requirements
- Regression Test - Re-running all tests on system when it is changed
Spare the Unit Testing
Top 5 Excuses for Not Doing Unit Testing
- I don’t have time to do the unit test.
- The office pays me to write down the codes, not to write down unit test.
- I am supporting a legacy application without unit tests and existing design is not suitable for unit test.
- QA and User Acceptance Testing are far more effective in finding bugs.
- I don’t know how to write unit test.
Advantages of Unit Test
- Reduces the level of bugs in production code
- Saves your development time
- Automated tests can be run as frequently as required
- Make easier to change and refactor code
- Improve the design of code especially with Test-Driven-Development
- A form of documentation
- Inspires confidence!
- Measures of completion
Disadvantages of Unit Test
- Not implementable to logical operators
- Insensitive to loop operators (number of iterations)
Best Practice and Principles to Write Unit Testing
Principles to Write Unit Testing
Principle 1. “Test the logic of the class only, nothing else”
Note that this is one of the most important principles during unit testing. When you are going to test a class, you should not have dependency on database, file, registry, web services, etc. You should be able to test any class in complete "isolation" and it should be designed to support complete "isolation."
According to this principle, mock out all external services and states and remember unit test NEVER uses-
- configuration settings
- a database
- another Application/Service/file/network I/O
- logging
Principle 2. “Fail first and set a guard at the door"
Before you write piece of logic, first write your fail test. After that, add or refactor your guard logics and throw proper exceptions or messages for the negative or exceptional data. Finally, run it and pass the test.
Explanation of the Unit Testing Principles
Principle 1: Suppose, you have a method 'IsValidUser
' to verify the user name and password for login.
Now if you look at the line number 15 and 17, then you will see class 'UserLogIn
' has a dependency to the class 'UserDataAccess
' and it is tightly coupled. Now the problem is you need to test a method 'IsValidUser
' of a class 'UserLogIn
' and you have to avoid the dependency. Because if you call 'IsValidUser
', then it will call the method 'GetUserInfoByUseName
'. According to the unit test principle, if we have a dependency from one method to another outsider class, then we have to mock object for that outsider class. It means that we have to inject some dummy data into the outsider classs-object. So,In this example we need to inject dummy data for the 'GetUserInfoByUseeName(userName)
. If we do so, then we will able to verify the logics of the method 'IsValidUser
' in the 'UserLogIn
' class.
So, avoid the tight coupling code and refactor your the code again. Find another one of my articles to know dependency, tight coupling and loose coupling. Now my main goal to make you know the unit testing principle only. Anyway, let's refactor the class.
Now the class 'RefactoringUserLogIn
' is loosely coupled and you can inject the implemented class of the 'IUserDataAccess
' interface which will make your life easy to do unit testing. Note that there are many ways to inject the implemented class. Find another of my articles to know how to mock an object of a class. During Unit Testing, if you mock the 'IUserDataAccess
', it means you are implementing a dummy object of 'IUserDataAccess
' interface and the method 'IsValidUser
' will not able to get any data directly from outside like the database or ORM via 'UserDataAccess
' class.
Principle 2
For the first time, more than 50% people fail their road driving test, then practice on the highway, seeking tips from others, they are ensuring the success in the road test. Anyway, If you are already familiar with Test-Driven-Development (TDD), then you are already familiar about the fail test. I will explain TDD to my another article.
Let’s explain the guard logic and how it will help you to pass your unit testing. Say, we have a method ‘GetSumByPositiveNumber
’ and it has two parameters. The business requirement is always input the positive numbers and get returns the sum. Never input a negative or zero number.
Now according to your business requirement, if you pass the values of parameters like 1 and 2, then the method will return 3 which is correct. But what happens if you pass the negative values as a parameter or some combination like (-5, -1), (-5, 1), (5, 0), (0, 0), etc.?
But if we put some guard logics into the method at the beginning, then the test should be passed. Because we are throwing the exception for the negative data.
Best Practices to Write Unit Testing
- If old test fails for the extension of the method which is introduced, then remove or enhance old Test and avoid requirement conflict.
- If you remove a single line in your tested class and all tests still pass, then you don't have enough unit tests.
- Developer will often run the test, so make test easy to run.
- Test only
public
methods and public
interface of the component - Don't create instances of classes directly inside a unit test - use factory method.
- Refactor the unit test and Keep It Simple. Remember that tests must be maintained by any code - so better to keep it simple.
- Any test should run in any order; so avoid dependencies between tests.
- Test should be readable like a book; so write comments in Asserts. Write descriptive method names. Use Behavior-Driven-Development (BBD) Technique.
- Test everything that could possibly break.
- Test everything, but not
Private
methods
Writing Test Scenario, Test Cases and Test Data
Only 3 Test Cases for Unit Testing, Nothing Else
- Positive Test Cases: Correct data to check for correct output
- Negative Test Cases: Broken or missing data to check for proper handling
- Exception Test Cases: Giving unexpected data or behavior and check for the exception caught properly or not
Test Data According to The Test Cases
Let’s set some test data and consider “GetSum
” as an example. Here, my main goal is to show you how we can set some Test Data for the test cases.
Types of Test Data
- Positive data
- Negative data
- Exceptional data
Positive Data for Positive Test Cases
The main goal of the positive Test cases is to verify the functionality of the logics.
TestCase-1
: Given positive values, should return expected result
TestData-1
: Set Input Parameters as firstNumber =1
, secondNumber=1
Negative Data for Negative Test Cases
The main goal of the negative Test cases, to get proper messages for the bad input according to your business requirements.
TestCase-2
: Given invalid values, should produce invalid argument message:
TestData-2
: Set Input Parameters as firstNumber =-1
, secondNumber =-1
TestData-3
: Set Input Parameters as firstNumber =-1
, secondNumber = 1
TestData-4
: Set Input Parameters as firstNumber = 0
, secondNumber = 1
TestData-5
: Set Input Parameters as firstNumber = 0
, secondNumber =-1
TestData-6
: Set Input Parameters as firstNumber = 0
, secondNumber = 0
, etc.
Exceptional Data for Exception Test Cases
The main goal of the Exceptional Test cases is to find out the proper exception handling with proper messages. So that your code can’t break for the threshold limits.
Here, you can set the threshold limits of your test data. In DOT NET, the minimum value for a variable of type int
is -2147483648
and the maximum value for a variable of type int
is 2147483647
.
TestCase3
: Given threshold limit values, should throw exception message:
TestData-7
: Set Input Parameters as firstNumber =1
, secondNumber =2147483649
TestData-8
: Set Input Parameters as firstNumber =1
, secondNumber = -2147483649
TestData-9
: Set Input Parameters as firstNumber =2147483649
, secondNumber = -2147483649
TestData-10
: Set Input Parameters as firstNumber =2147483647
, secondNumber=2147483647
, etc.
Naming Convention Of Test Method For Unit Testing
Traditional Principle of Unit Test
One Test Method is written to test one and only one method and one assert
method should test only one expectation at a time.
In short, the principle says – “one function/method and one assert per test method”.
So, let’s consider the below example:
Comparing the Traditional Principle to the Real World
Test Scenario
Verify the “GetSum
” method.
Test Cases
Positive Test Cases
TC1
: Given positive values, should return expected result Test Data-1
: firstValue =5
, secondValue =6
Negative Test Cases
TC2
: Given zero values, should produce invalid argument message Test Data-2
: firstValue =0
, secondValue =0
TC3
: Given negative values, should produce invalid argument message Test Data-3
: firstValue =-5
, secondValue =-6
Exceptional Test Cases
TC4
: Given threshold limit values, should throw exception message Test Data-4
: firstValue =2147483647
, secondValue =2147483647
Test Method Example
Now according to the traditional principle, let’s write the test method for the “GetSum
”
Now according to the traditional principle, we have covered the Positive Test Case with “Test Data-1
”. But what about negative and exceptional test cases??
How do we cover the negative and exceptional test cases with the traditional principle??
Behavior Driven Development (BDD)
Why do We Need BDD
If we want to cover all of the behaviors of our test cases according to our previous example, then we need to follow some technique so that we can write down all of the behaviors of the method. So, BDD is the technique which gives us the opportunity to fulfill all of the test cases with standard and readable naming convention. Many people, many minds. There are many techniques to write the naming convention of the test method. But it really depends on you and your preference. There is nothing right or wrong if you follow some other technique. Anyway, in short, we can say that in BDD, components test their expected behavior.
Concept of BDD
- Given I am a beginner to the BDD technique, and I have never used this technique before
- When I read this tutorial for BDD
- Then I started to like it and finally I learn it.
BDD Naming Convention
Test Scenario
Verify the “GetSum
” Method
Test Cases
Positive Test Cases
TC1
: Given positive values, should return expected result Test Data-1
: firstValue =5
, secondValue =6
Test Method - Naming Convection:
GivenPositiveVaidValuesAsParams_WhenGetSumIsCalled_ThenItShouldReturnSumValue
More Readable:
Given_Positive_Vaid_Values_As_Params_When_GetSum_Is_Called_Then_It_Should_Return_Sum_Value
Negative Test Cases
TC2
: Given zero values, should produce invalid argument message Test Data-2
: firstValue =0
, secondValue =0
Test Method - Naming Convection:
GivenZeroValuesAsParams_WhenGetSumIsCalled_ThenItShouldThrowInvalidArgumentException
More Readable -
Given_Zero_Values_As_Params_When_GetSum_Is_Called_Then_It_Should_Throw_Invalid_Argument_Exception
TC3
: Given negative values, should produce invalid argument message Test Data-3
: firstValue =-5
, secondValue =-6
Test Method - Naming Convection:
GivenNegativeValues_WhenGetSumIsCalled_ThenItShouldThrowInvalidArgumentException
More Readable -
Given_Negative_Values_When_GetSum_Is_Called_Then_It_Should_Throw_Invalid_Argument_Exception
Exceptional Test Cases
TC4
: Given threshold limit values, should throw exception message Test Data-4
: firstValue =2147483647
, secondValue =2147483647
GivenMaxLimitValuesOfIntAsParams_WhenGetSumIsCalled_ThenItShouldThrowSumException
More Readable -
Given_Max_Limit_Values_Of_Int_As_Params_When_GetSum_Is_Called_Then_It_Should_Throw_Sum_Exception
History
- 12th April, 2017: Initial version