First of all, based on the title, it doesn’t mean to choose between unit testing and functional testing. Let’s admit that both have their own importance in software development. One thing I would like to highlight is that many developers think that unit tests are not useful and waste of time. James Coplien wrote the blog “Why Most Unit Testing is Waste” and it drove huge discussion on Reddit and Hacker News. The whole developer community was divided into two sides: Unit testing supporters and Unit testing Haters.
However, in functional testing, we don’t have contrary opinions. Functional testing is done from the user’s perspective and its utility is well understood by users, developers and QA alike. But at the end users only care about the functionality and UI of the application.
My goal with this blog post is to help you understand the different aspects of unit testing vs functional testing. You will get to know when both the testing comes into the picture and which type of defects they report. Also, we’ll see an example of how both the testing help deliver a quality product if we keep a good balance.
Differentiating Factors of Unit Testing vs Functional Testing
These factors will help you to understand the role of both the testing types in the software testing process. Before going into depth, take a look at below image for differences between unit testing vs functional testing.
Who perform unit testing and functional testing?
Unit Tests makes up the largest section of the pyramid, forming a solid base. Unit tests are easy to create and run and hence they require a low cost. Because no. of unit tests are high, Developers use different unit testing tools to automate them based on the programming language and framework they use.
Unit testing is a software testing method by which individual units of code are tested in isolation. The purpose of unit testing is to isolate the smallest testable parts of an API and verify that they function properly in isolation. A unit test can verify different behavioral aspects of the system under test(SUT), but mainly it verifies that the SUT produces the correct results.
From the developer’s perspective, the purpose of unit testing is to create a robust codebase with a minimal cost. Another important purpose of unit tests is to provide documentation for high-level testing, i.e. Integration testing and Functional testing.
Simply, functional testing is the interaction with the system the way user the does. The tester isn’t concerned with the actual code, rather he/she need to verify the output based on given the user requirements with the expected output.
The prime objective of Functional testing is checking the functionalities of the system. Functional tests check the entire application, its hardware, and networking infrastructure, from the front end UI to the back-end database systems. In that sense, functional tests are also a form of integration testing, ensuring that different components are working together as expected.
Unlike unit tests, the functional tests don’t tell you what is broken or where to locate the failure in the code base. They just tell you something is broken. That something could be the test, the browser, or a race condition. There is no way to tell because functional tests, by definition of being end-to-end, test everything.
According to Ward Cunningham, “Functional test and Unit test serve different purposes. One gives the developer confidence when refactoring, the other gives the customer confidence when planning.”
How do they improve the software quality?
By writing a test to drive your newly written code, you essentially capture that work. If in, say, six months you or another programmer breaks the code, you can use that test to fix the code. This is how unit-tests protect against regressions.
Defects in objects are identified nearly immediately, so there is less re-work and re-test dependency on other code.
Complex applications have just too many variables to thoroughly test in a practical way.
Breaking them down into manageable unit tests is cheaper to develop and maintain.
It generates a good bug report which could answer these 4 questions:
- Which component is under test?
- What is the expected behavior?
- What was the actual result?
- What is the expected result?
It ensures the whole app or system works as expected. Typically have thorough tests for “happy paths” — ensuring the critical app capabilities, such as user logins, signups, purchase workflows, and all the critical user workflows all behave as expected.
There are many types of functional testing and each has their own benefits. For example, Integration testing checks that all the integration are working together. Smoke testing checks the critical features of the build and saves time and reduce regressions.
System testing validates the entire software/app in the context of real user scenarios. End-to-End tests increase test coverage and reduce the risk associated with integrating new code into an application.
When to perform?
Unit tests aren’t a replacement for functional testing. But they are the solid foundation on which the rest of your testing process should be built.
The best practice is that you should start writing your tests when you start writing your code. Test Driven Development (TDD) is a popular software development practice which advocates writing of tests before the code.
Over time you will see the benefits of writing unit-tests. One example, you can refactor your code and the unit-tests will tell you if you broke anything.
Integration testing comes after the unit testing and it is a type of functional testing. In that way, functional testing starts when two modules interact with each other. After that, testers perform functional testing on the feature. You might use a unit test to test an individual function and an integration test to check that two parts of the play nice. Functional tests are on a whole another level. While you can have hundreds of unit tests, you usually want to have only a small amount of functional tests.
Type of testing technique: White box or Black box?
In white-box testing, an internal perspective of the system, as well as programming skills, are used to design test cases. The usual criteria for white-box testing are the execution path and data structure sensitization. These are sometimes called “branch testing”, “path testing”, “data flow testing”.
Based on the unit testing definition and purpose, unit testing is a white box testing technique. Also, unit-test sensitizes all of the execution paths and data structures in the unit that you are testing, which is again a white box testing.
Whereas functional testing is a black box testing technique in which the functionality of the software is tested without looking at the internal code structure. It is done against user/business requirements where we compare the resultant output with expected output.
Purpose of Test Coverage for both the testing is different
Test coverage is one of the important metrics in software testing. It serves the different purpose for both functional testing and unit testing.
A test coverage tool simply keeps track of which parts of the code get executed and which not. In the context of unit testing, we can call it a code coverage. High code coverage gives developers confidence that their entire project is well developed and maintained.
There’s also a common belief that high code coverage improves the code quality. If unit tests are written poorly with high test coverage then it can’t assure the better code quality.
For functional testing, test coverage can establish Traceability between the requirements and test cases. Functional Test Coverage should give an indication of what features are done, in that they satisfy the acceptance criteria, and what features are still in progress. This sort of information is much more accessible to product owners than the number of lines of code exercised.
Complexity in writing test cases
We will see the complexity of unit testing when done in a TDD environment. As you know, in TDD, you need to write unit tests first before writing the production code. Here you need to think about the design of the code upfront based on the requirements. Sometimes the design is not clear at the start and evolves as you go along – this will force you to redo your test which is very time-consuming. For example, algorithms that change frequently take too much time. I would suggest postponing unit tests in this case until you have some idea of the design in mind.
In real-time scenarios, we have to deal with other dependencies and integrations like user interface, database etc. Here TDD gets really hard and it involves many abstractions: mock objects, programming to an interface, MVC/MVP patterns etc. It requires a lot of knowledge and developers have to write lots of unit tests, sometimes, more than actual code.
Apart from dependencies, unit testing becomes very complex when you test trivial code. Because these code units tend to glue together and orchestrate interactions between other code units. See the below tweet by Dan Abramov where he suggests to write unit tests for non-trivial code.
Unit tests are great for non-trivial code. Integration tests are great for mission-critical code. But if you’re building a regular consumer product and trying to achieve 100% coverage, I don’t believe it is optimal.
— Dan Abramov (@dan_abramov) December 24, 2017
In unit testing, we can mock out all dependencies, but for functional testing, it’s a different story. A good example would be testing a simple screen that loads the data from the network – it will fail every time you run it while offline. Such scenarios make functional testing complex.
Generally, developers split their time between developing new features and fixing defects. QA work is much more diverse. A substantial portion of the release is spent on testing functionality which was already released and used. While the main effort of designing and writing test plans for this functionality is invested in past releases, every new feature developed requires a test plan and a maintenance effort on existing tests. One of the main goals for QA managers is reducing this effort to a minimum. Teams that succeed in creating clear and reusable tests simplify the maintenance struggle and optimize the test registration time.
How they improve the software quality?
Without unit testing, the time it takes to debug or resolve a functional test that may have failed takes a long time to track down. With unit testing, however, the scope of the test is kept to a minimum, and the point at which a failure may be triggered can be isolated faster.
Unit tests make our codebase more maintainable because they give us confidence in making changes and helps us understand what the correct behavior should be. They also make our code more flexible and comprehensible because they encourage techniques like dependency injection, composition, and programming to abstractions using mocking and stubbing.
Functional testing covers issues of the whole feature or application. When the system under test doesn’t respond as per the expected output, it is considered as a functional issue. For example, Search returns the wrong results, clicking on a link takes you to page X instead of the intended page Y, the app crashes etc.
Keep Balance of both Functional Testing and Unit Testing
There are no universally defined rules about the correct balance between unit and functional tests. The number of tests varies for different projects based on the circumstances.
But you can make the best decision by thinking about various tests according to the project requirements and asking whether they are delivering the benefits you want.
Over-reliance on functional testing can wreak the quality of the application.
Over the years, Software engineering has been evolved continuously. However, the one area we haven’t progressed at all or even regressed is in the area of Software Quality Assurance. The reason is that many companies heavily rely on functional testing and neglect the unit testing.
Let’s see how over-reliance on functional testing and absence of unit testing wrecks the quality of the application:
- Functional testing is performed after the feature has been developed and hence they do not provide feedback early in SDLC.
- Unit Tests can cover most edge cases and with a wider range of input and output. Contrary It’s difficult to cover all the edge cases with functional testing due to time and cost constraints.
- When there’s no unit testing performed by developers, chances of bugs showing in production are high. I have found testers and developers having to spend extra hours to solve issues in production. It takes lots of time as functional tests can’t identify the exact location of the issue.
- Code without Unit Tests is hard to change or refactor as nobody knows for sure what would break when a certain piece of code is changed. This often leads to regression issues as changes end up breaking existing functionality. If the same piece of code is covered through Unit Tests, a breaking test would alert the developer of an impending disaster.
Every software development project has three basic objectives – correctness of the features, clean and maintainable code and productive workflow. You can prepare a basic set of questions for a product manager to complete these objects and keep a balance of unit and functional tests.
To find the right balance between all three test types, the best visual aid to use is the testing pyramid. The bulk of your tests are unit tests at the bottom of the pyramid. As you move up the pyramid, your tests get larger, but at the same time, the number of tests (the width of your pyramid) gets smaller.
Google often suggests a 70/20/10 split: 70% unit tests, 20% integration tests, and 10% end-to-end tests. The exact mix will be different for each team, but in general, it should retain that pyramid shape.
It is important for Developers and Testers to distinguish between the intent and scope of Unit tests vs Functional tests. Because both the tests are used for different purposes, they aren’t interchangeable. Both the tests have their own advantages and limitations.
- Unit testing is fast and helps writing clean code, but doesn’t provide confidence that the system will work as expected. Basically, it tells us that where is the problem in the code.
- Functional testing is slow and complex but it ensures that the system will work according to the requirements. Basically, it tells us that what is the problem in the functionality.
The ultimate goal of testing is to deliver the quality product. Now your job is to find the right balance between Unit testing and Functional testing.