Unit testing is a software testing technique in which individual components of the code are tested to ensure they work as expected. Each unit test should focus on a single function, class, or method and provide immediate feedback on whether that piece of code is functioning correctly. The goal is to catch and address bugs early in development, ensuring a stable and maintainable codebase.
- Improved code quality: Unit tests ensure that code functions as expected before it is merged into the main codebase.
- Early bug detection: Tests provide an opportunity to catch and fix issues during development.
- Facilitates refactoring: With a solid unit test suite, developers can confidently refactor code knowing tests will highlight regressions.
- Code coverage: Tests help measure how much of the codebase is covered, ensuring critical functionality is always tested.
Historically, this project has used both NUnit and the Unity Test Framework for unit testing. However, moving forward, we are transitioning to using NUnit exclusively. NUnit offers more flexibility and integration options for broader test case management and is better suited for the long-term needs of this project.
While there are still some existing tests using the Unity Test Framework, all new tests should be written using NUnit. This will ensure consistency across the test suite and streamline the development process.
- Flexibility: NUnit supports a wide range of assertions, test case parameters, and test fixtures.
- CI/CD Compatibility: NUnit can be easily integrated into Continuous Integration and Deployment pipelines (planned for future).
- Broad usage: NUnit is a widely-adopted framework, making it familiar to many developers and well-supported across different IDEs.
Unit tests should be written for scripts used in the sample scenes and for the core plugin code. The following guidelines outline the scope:
- Core plugin functionality (e.g., helper classes, business logic, utilities, etc.).
- Scripts attached to game objects or components in the sample scenes (but not the scenes themselves).
- The EOS SDK, and it's C# wrapper. The behavior of the SDK and the C# wrapper provided with it is (currently) outside the scope of this project’s unit tests.
- Sample scenes (for now). Tests should focus on scripts, not scene setups or interactions between objects within the scenes.
Every unit test should follow these basic steps:
- Setup: Initialize the objects or dependencies that will be tested.
- Act: Execute the method or function you are testing.
- Assert: Verify that the results are what you expect.
[TestFixture]
public class ExampleTest
{
[Test]
public void Addition_ShouldReturnCorrectSum()
{
// Arrange
int a = 5;
int b = 3;
// Act
int result = a + b;
// Assert
Assert.AreEqual(8, result);
}
}
Any new functionality added to the project should come with complete unit test coverage. The goal is to ensure that every feature introduced is tested thoroughly.
When refactoring existing code, make sure that the unit tests are updated accordingly. Refactors should never lead to a reduction in test coverage.
While there is no current implementation for mocking the EOS SDK, this functionality is in development. Once mocking support is available, you will be able to write unit tests that mock dependencies on the EOS SDK.
For now, focus on isolating business logic and core plugin functionality in your tests.
As we transition fully to NUnit and increase our test coverage, the goal is to reach and maintain high code coverage across the entire project. To facilitate this, developers should:
- Write tests for all new code.
- Update tests when refactoring or modifying existing code.
- Ensure tests are well-structured, easy to read, and maintainable.
- Small, isolated tests: Each test should focus on a single function or method.
- Meaningful names: Test methods should clearly describe the scenario being tested.
- Consistency: Follow a consistent structure for arranging, acting, and asserting in your tests.
- Avoid testing too much at once: Unit tests should focus on small, testable components, not entire systems.
- Repeatable and fast: Tests should be able to run quickly and consistently without dependencies on external services or the environment.
To maintain clarity and consistency, unit test names should follow these conventions:
-
Use Descriptive Names: Test names should clearly describe the scenario being tested and the expected outcome. Follow the pattern:
MethodName_Scenario_ExpectedOutcome
- Example:
CalculateTotal_WithValidInputs_ReturnsCorrectSum()
- Example:
-
PascalCase: Use PascalCase for test method names, capitalizing each word.
- Example:
Login_WithInvalidPassword_ThrowsException()
- Example:
-
Group Tests by Class or Method: Create test classes named after the class being tested, with
Tests
appended.- Example:
OrderProcessorTests
- Example:
-
Arrange-Act-Assert: Use comments or whitespace to clearly separate the three stages of each test.
- Example:
[Test] public void GetPrice_WithDiscountApplied_ReturnsDiscountedPrice() { // Arrange var product = new Product(100); product.ApplyDiscount(10); // Act var result = product.GetPrice(); // Assert Assert.AreEqual(90, result); }
-
One Assertion Per Test: Focus on testing one aspect of the method or functionality in each test. If multiple assertions are needed, ensure they are related to the same scenario.
- Example:
SaveOrder_WhenOrderIsValid_CreatesNewOrderRecord()
- Example:
-
Edge Cases: Use specific names when testing edge cases or invalid inputs.
- Example:
Withdraw_WithInsufficientFunds_ThrowsInsufficientFundsException()
- Example:
-
Avoid Ambiguity: Test names should avoid ambiguous phrases such as "Can" or "Does".
- Example:
ProcessPayment_WithValidCreditCard_ReturnsSuccess()
- Example:
-
Setup and Teardown Methods: Use clearly named
[SetUp]
methods for common setup logic.- Example:
InitializeOrderProcessor()
- Example:
Following these conventions will ensure that tests are easy to read, maintain, and extend over time.
To further your understanding of unit testing and best practices, the following resources are highly recommended:
This guide provides a primer for adding unit tests to this Unity project. Following the outlined principles will ensure high test coverage, improved code quality, and a stable development environment.