How to make a unit test Part.1 Design pattern for introduction

Click here for Part.2

In order to execute unit tests, it is necessary to substitute / eliminate environment-dependent elements such as devices. If you do not carefully design these handlings, you will not be able to unit test. In a series of articles in this title, we will consider implementations for testing and production, using design patterns. In this article, we will consider how to handle data held on-memory, but the same applies to devices such as networks.

Ultimately, the focus is on how to replace the test module. This idea is called Dependency Injection (DI). In DI, the degree of coupling between modules is reduced via Interface.

The sample program is written in C ++, but C # and Python will also be written.

change history

2015-04-26 Added Python sample code

Environment-dependent data

The data can be anything that determines the behavior of the application or conveys the result of the behavior. You can do it locally, but there are times when you really want to use it globally. How should it be retained?

The following example assumes a function that changes its behavior depending on the version of the external module.

//Before introduction (untestable or difficult)
void func() {
    //Get version
    int version = SomeModule::getMajorVersion();
    if (version > 10) {
        //Something to do with the specified version
    }
}

Here, it is assumed that SomeModule :: getMajorVersion () can be used only in a specific environment, or it is difficult to switch the state. With such code, it is difficult to test the if statement that follows.

Use Proxy pattern

Here, create a dummy class that replaces SomeModule, prepare an entity and a Proxy that accesses the dummy, and make it testable. First, from the Proxy class Interface that serves as the window.

class ModuleProxy {
	virtual int getMajorVersion_() const = 0;
protected:
	ModuleProxy() {}
public:
	virtual ~ModuleProxy() {};
	int getMajorVersion() const {
		return this->getMajorVersion_();
	}
};

Next is the entity class and dummy class. Here, the original SomeModule is also hidden and the usage class is created.

//A class that actually calls an external module
class SomeModuleImpl : public ModuleProxy {
	virtual int getMajorVersion_() const override {
		return SomeModule::getMajorVersion();
	}
};

//Dummy class for testing
class ModuleDummy : public ModuleProxy {
	virtual int getMajorVersion_() const override {
		return major_;
	}
public:
	ModuleDummy(int ver = 0)
		: major_(ver)
	{}
	int major_;
};

The func function and the test code are as follows.

void func(ModuleProxy& module) {
    //Get version
    int version = module.getMajorVersion();
    if (version > 10) {
    }
}

void XXXTest::funcTest() {
   	ModuleDummy dummy(10);
    {
        func(dummy);
    }
    {
    	dummy.major_ = 11;
        func(dummy);
    }
}

Use of Monostate pattern

Another way to implement it is to use the Monostate pattern. Here, the member function is also static, but it can be a normal function.

class Data {
    static int majorVersion;
public:
    static int getMajorVersion() const {
        return majorVersion;
    }

    friend class XXXTest;
};
int Data::majorVersion = 1;

The application implementation that uses this is as follows.

void func() {
    if (Data::getMajorVersion() > 10) {
    }
}

The test side looks like this.

void XXXTest::funcTest() {
    {
        Data::majorVersion = 10;
        func();
    }
    {
        Data::majorVersion = 11;
        func();
    }
}

The Monostate pattern is simple, but you need to think about value initialization. When referenced by other static elements, care must be taken in the order of initialization.

Sample code (Python)

The Python version of the Monostate pattern is shown below. Classmethod was specified for the method.

class Data:
    _majorVersion = 1

    @classmethod
    def getMajorVersion(cls) -> int:
        return cls._majorVersion

The test side looks like this:

import unittest

class Test_testSample(unittest.TestCase):
    def test_version(self):
        Monostate.Data._majorVersion = 10
        self.assertFalse(Target.func())
        Monostate.Data._majorVersion = 11
        self.assertTrue(Target.func())

Summary

In this article, DI was realized using the Proxy pattern. We also showed how to use the Monostate pattern more simply.

Neta from the next time onwards

It's important to be aware of contracts, even in languages such as C ++ / C # / Python that don't support contract functionality.

Since Monostate can be used polymorphically, it is possible to incorporate Proxy patterns to make it easier to use.

It's good to use friend in the unit test, but it will add friend to the header on the application side, which will cause recompilation. Think about how to deal with it.

When implementing resource access with shared_ptr, it may be better to use weak_ptr as the IF of Proxy. Try these enhancements while still testable.

Recommended Posts

How to make a unit test Part.1 Design pattern for introduction
How to make unit tests Part.2 Class design for tests
Spigot (Paper) Introduction to how to make a plug-in for 2020 # 01 (Environment construction)
How to make a shooting game with toio (Part 1)
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit Part 2-
How to write a test for processing that uses BigQuery
How to make a Japanese-English translation
How to make a slack bot
How to make a crawler --Advanced
How to make a recursive function
How to make a deadman's switch
[Blender] How to make a Blender plugin
How to make a crawler --Basic
[Introduction to Python] How to use the in operator in a for statement?
[Python] How to make a class iterable
[Cocos2d-x] How to make Script Binding (Part 2)
How to make a Backtrader custom indicator
How to test on a Django-authenticated page
How to make a Pelican site map
Make a chessboard pattern for camera calibration
[Cocos2d-x] How to make Script Binding (Part 1)
How to make a model for object detection using YOLO in 3 hours
How to make a dialogue system dedicated to beginners
How to make a dictionary with a hierarchical structure.
How to make a QGIS plugin (package generation)
How to write a ShellScript Bash for statement
How to create a shortcut command for LINUX
I read "How to make a hacking lab"
How to make Substance Painter Python plugin (Introduction)
How to make Python faster for beginners [numpy]
[Introduction to Python] How to get the index of data with a for statement
Tips for Python beginners to use the Scikit-image example for themselves 7 How to make a module
[Go] How to create a custom error for Sentry
[Introduction to python] A high-speed introduction to Python for busy C ++ programmers
How to unit test a function containing the current time using freezegun in python
How to make a Python package using VS Code
Introduction to PyQt4 Part 1
How to create a local repository for Linux OS
How to build a development environment for TensorFlow (1.0.0) (Mac)
Basics of PyTorch (2) -How to make a neural network-
Write code to Unit Test a Python web app
[Introduction to Python] How to write repetitive statements using for statements
How to make a Cisco Webex Teams BOT with Flask
[Python] How to make a list of character strings character by character
How to shuffle a part of a Python list (at random.shuffle)
How to make a multiplayer online action game on Slack
Experiment to make a self-catering PDF for Kindle with Python
How to define multiple variables in a python for statement
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit edition-
How to make a simple Flappy Bird game with pygame
How to substitute a numerical value for a partial match (Note 1)
To write a test in Go, first design the interface
How to create a SAS token for Azure IoT Hub
[Introduction to Tensorflow] Understand Tensorflow properly and try to make a model
A quick introduction to pytest-mock
Make a Blueqat backend ~ Part 1
Make a Blueqat backend ~ Part 2
I wrote unit tests for various languages
Give pytest clean parameters for flask unit tests
Python unit tests
How to make unit tests Part.2 Class design for tests
A super introduction to Linux
How to call a function
How to hack a terminal