Simple C++ Test class with rule of 5 It has been a long time I used c++.
So I brushing over concepts for interview preparation.
Can you please help me with code review?
If you have any observations/bugs/alternatives please point out.
For better formatting, I have shared code here feel free to open issues. :)
Thanks a ton in advance.

#include &lt;cstring&gt;
#include &lt;memory&gt;
#include &lt;utility&gt;

class Test {
public:
    //Default constructor
    Test() = default;

    //Copy constructor
    Test(const Test&amp; other) : m_size(other.m_size), m_buffer(new char[m_size]()) {
        strncpy(m_buffer.get(), other.m_buffer.get(), m_size);
    } 

    //Parametarized constructor
    Test(std::size_t len, const char* data) : m_size(len), m_buffer(new char[len]()) {
        strncpy(m_buffer.get(), data, m_size);
    }

    //Move constructor
    Test(Test&amp;&amp; other) noexcept {
        Test temp(other);
        swap(*this, temp);
    }

    //Assignment operator
    Test&amp; operator=(Test other) {
        if(this != &amp;other) {
            swap(*this, other);
        }
        return *this;
    }

    //Move assignment operator
    Test&amp; operator=(Test&amp;&amp; other) noexcept {
        if(this != &amp;other) {
            Test temp(other);
            swap(*this, temp);
        }
        return *this;
    }

    //Destructor
    ~Test() {
        m_size = 0;
        m_buffer = nullptr;
    }
private:
    std::size_t m_size = 0;    
    std::unique_ptr&lt;char[]&gt; m_buffer = nullptr;

    static void swap(Test&amp; first, Test&amp; second) noexcept{
        using std::swap;
        swap(first.m_size, second.m_size);
        swap(first.m_buffer, second.m_buffer);
    }
};
Observations
Your rule of 5 implementation is basically wrong. You are implement copy operations for both copy and move (which means you may as well not have the move operations).
Your destructor is not doing any real work and thus adding unneeded code.
Your swap function does not work as expected as it can not be used with  Koenig lookup (ADL) and thus in most standard library would be replaced with std::swap resulting in a sub optimal swap.
Details
Here I don't see the need for creating a temporary.
    //Move constructor
    Test(Test&amp;&amp; other) noexcept {
        Test temp(other);
        swap(*this, temp);
    }

By creating the temporary your move is basically just as expensive as your copy. The whole point of the move is that it is cheap.
I would simply swap the current with other.
    //Move constructor
    Test(Test&amp;&amp; other) noexcept {
        swap(other);
    }

After the move construction the content of other has no guaranteed state (only that it is valid). Since you define m_size and m_buffer to auto initialize the current this is in a valid state and can simply be swapped with other.
    // Note I usually define a `noexcept` swap method.

    void swap(Test&amp; other) noexcept {
        using std::swap;
        swap(m_size,   other. m_size);
        swap(m_buffer, other. m_buffer);
    }

I also define swap() with two parameters as a free standing function that simply calls the swap method. This is because the compiler can using Koenig lookup (ADL) to find the correct swap function.
    void swap(Test&amp; lhs, Test&amp; rhs) {
        lhs.swap(rhs);
    }


The Copy assignment is sub optimal.
    //Assignment operator
    Test&amp; operator=(Test other) {
        if(this != &amp;other) {
            swap(*this, other);
        }
        return *this;
    }

Since the parameter other is created via a copy construction it is guaranteed not to be the same as this. Thus the test if(this != &amp;other) is just a pessimization as it is not needed.

Again you perform a copy during the move assignment.
    //Move assignment operator
    Test&amp; operator=(Test&amp;&amp; other) noexcept {
        if(this != &amp;other) {
            Test temp(other);
            swap(*this, temp);
        }
        return *this;
    }

This defeats the purpose of the move (as it is supposed to be cheaper than a copy). You can simply swap this and other as they should both be valid.
    Test&amp; operator=(Test&amp;&amp; other) noexcept {
        swap(other);
        return *this;
    }

No need to check for self assignment as this is a pessimization of the normal more common situation. Even of they are the same object the swap will work correctly.

The destructor is useless:
    //Destructor
    ~Test() {
        m_size = 0;
        m_buffer = nullptr;
    }

The member: m_buffer will correctly clean up the memory.

Making this a static member is not doing you any favors as it prevents Koenig lookup (ADL).
    static void swap(Test&amp; first, Test&amp; second) noexcept{
        using std::swap;
        swap(first.m_size, second.m_size);
        swap(first.m_buffer, second.m_buffer);
    }

Make this a free standing function:
namespace PL
{

class TestStatic
{
    public:
        static void swap(TestStatic&amp; lhs, TestStatic&amp; rhs) noexcept
        {}
};


class TestFreeStand
{
    public:
};
void swap(TestFreeStand&amp; lhs, TestFreeStand&amp; rhs) noexcept
{}

}


int main()
{
    PL::TestStatic      staticA;
    PL::TestStatic      staticB;

    swap(staticA, staticB);


    PL::TestFreeStand   freeStandA;
    PL::TestFreeStand   freeStandB;

    swap(freeStandA, freeStandB);
}

Notice the only compilation error here is: swap(staticA, staticB);
This means that if you add using std::swap; this code will now compile but it will not use your static void swap() method it will use std::swap which makes a copy of the object. The point of writing your own version of swap is that you are providing an optimization over the standard swap.
Better Implementation
#include &lt;cstring&gt;
#include &lt;memory&gt;
#include &lt;utility&gt;

namespace Testing
{

    class Test
    {
        public:
            // Default constructor
            Test() = default;
        
            // Parametrized constructor
            Test(std::size_t len, const char* data)
                : m_size(len)
                , m_buffer(new char[len]())
            {
                // Should validate the input here.
                // It is the only time you get outside input.
                // Potentially you could throw here I choose
                // not too (because I can see it as a valid input)
                // Your use case may be different.
                if (data) {
                    strncpy(m_buffer.get(), data, m_size);
                }
            }
    
            // Copy constructor
            Test(const Test&amp; other)
                : m_size(other.m_size)
                , m_buffer(new char[m_size]())
            {
                strncpy(m_buffer.get(), other.m_buffer.get(), m_size);
            }     
        
            // Copy Assignment operator
            Test&amp; operator=(Test other)
            {
                swap(other);
                return *this;
            }
    
            // Move constructor
            Test(Test&amp;&amp; other) noexcept
            {
                swap(other);
            }
        
            // Move assignment operator
            Test&amp; operator=(Test&amp;&amp; other) noexcept
            {
                swap(other);
                return *this;
            }
        
            void swap(Test&amp; rhs) noexcept
            {
                using std::swap;
                swap(m_size,    rhs.m_size);
                swap(m_buffer,  rhs.m_buffer);
            }
        private:
            std::size_t m_size = 0;    
            std::unique_ptr&lt;char[]&gt; m_buffer = nullptr;
    };
    
    void swap(Test&amp; lhs, Test&amp; rhs) noexcept
    {
        lhs.swap(rhs);
    }
}