Unit Testing Principles, Practices, and Patterns

suojae·2024년 1월 23일
0

목록 보기
8/8


Part 1. The Bigger Picture

Chapter 1. The goal of unit testing

Sustainability and scalability are the keys. They allow you to maintain development speed in the long run.

Remember, not all tests are created equal. Some of them are valuable and contribute a lot to overall software quality. Others don’t. They raise false alarms, don’t help you catch regression errors, and are slow and difficult to maintain.

Code is a liability, not an asset. The more code you introduce, the more you extend the surface area for potential bugs in your software, and the higher the project’s upkeep cost.

// Bad Example
public static bool IsStringLong(string input)
{                                   1
     if (input.Length > 5)          1
         return true;               2

    return false;                   1
 }                                  1

public void Test()
{
    bool result = IsStringLong("abc");
    Assert.Equal(false, result);
}

//Modified Code
public static bool IsStringLong(string input)
{
    return input.Length > 5 ? true : false;
}

public void Test()
{
    bool result = IsStringLong("abc");
    Assert.Equal(false, result);
}

This simple example shows how easy it is to game the coverage numbers. The more compact your code is, the better the test coverage metric becomes, because it only accounts for the raw line numbers.

Likewise, targeting a specific coverage number creates a perverse incentive that goes against the goal of unit testing. Instead of focusing on testing the things that matter, people start to seek ways to attain this artificial target. Proper unit testing is difficult enough already. Imposing a mandatory coverage number only distracts developers from being mindful about what they test, and makes proper unit testing even harder to achieve.

The only point in having automated tests is if you constantly use them. All tests should be integrated into the development cycle. Ideally, you should execute them on every code change, even the smallest one. 이거 가능한거 맞나..ㅋㅋ

  1. It’s integrated into the development cycle.
  2. It targets only the most important parts of your code base.
  3. It provides maximum value with minimum maintenance costs.

The most difficult part of unit testing is achieving maximum value with minimum maintenance costs. That’s the main focus of this book.



Chapter 2. What is a unit test?

A unit test is an automated test that

  • Verifies a small piece of code (also known as a unit),
  • Does it quickly,
  • And does it in an isolated manner.

One benefit of this approach is that if the test fails, you know for sure which part of the code base is broken: it’s the system under test. There could be no other suspects, because all of the class’s neighbors are replaced with the test doubles.

Another benefit is the ability to split the object graph—the web of communicating classes solving the same problem.

Test double is an overarching term that describes all kinds of non-production-ready, fake dependencies in a test. Mock is just one kind of such dependencies.

The mock reacts to this request the way the tests need, regardless of the actual state of Store. In fact, the tests no longer use Store—we have introduced an IStore interface and are mocking that interface instead of the Store class.

Unit tests themselves should be run in isolation from each other. That way, you can run the tests in parallel, sequentially, and in any order, whatever fits you best, and they still won’t affect each other’s outcome.

A shared dependency is a dependency that is shared between tests and provides means for those tests to affect each other’s outcome. A typical example of shared dependencies is a static mutable field.

For example, a dependency on the database is both shared and volatile. But that’s not the case for the file system. The file system is not volatile because it is installed on every developer’s machine and it behaves deterministically in the vast majority of cases.

Shared dependencies almost always reside outside the execution process, while private dependencies usually don’t cross that boundary. Because of that, calls to shared dependencies, such as a database or the file system, take more time than calls to private dependencies.

[Fact]
public void Purchase_fails_when_not_enough_inventory()
{
    // Arrange
    var storeMock = new Mock<IStore>();
    storeMock
        .Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
        .Returns(false);
    var customer = new Customer();

    // Act
    bool success = customer.Purchase(storeMock.Object, Product.Shampoo, 5);

    // Assert
    Assert.False(success);
    storeMock.Verify(
        x => x.RemoveInventory(Product.Shampoo, 5),
        Times.Never);
}


Chapter 3. The anatomy of a unit test



Part 2. Making Your Tests Work For You



Part 3. Integration Testing



Part 4. Unit Testing Anti-Patterns

profile
Hi 👋🏻 I'm an iOS Developer who loves to read🤓

0개의 댓글