Ich habe verschiedene Artikel über Mocking vs Stubbing in Tests gelesen, einschließlich Martin Fowlers Spötteleien sind keine Spötteleien aber ich verstehe den Unterschied immer noch nicht.
Antworten
Zu viele Anzeigen?Sowohl Stubs als auch Mocks setzen externe Abhängigkeiten außer Kraft, aber der Unterschied ist
Kippen -> Zu den Testdaten
Verspottet -> So testen Sie das Verhalten
Fälschung/Pseudo -> Nichts testen (überschreiben Sie einfach die Funktionalität mit leeren Methoden, z.B. ersetzen Sie Logger
um Protokollierungsgeräusche während der Prüfung zu vermeiden)
Siehe unten Beispiel von Mocks vs Stubs mit C# und Moq Framework. Moq hat kein spezielles Schlüsselwort für Stub, aber Sie können Mock-Objekt verwenden, um auch Stubs zu erstellen.
namespace UnitTestProject2
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class UnitTest1
{
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
}
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(0);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
}
/// <summary>
/// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
{
// Arrange
var stubEntityRepository = new Mock<IEntityRepository>();
stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
.Returns("Stub");
const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
var entity = new EntityClass(stubEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
}
}
public class EntityClass
{
private IEntityRepository _entityRepository;
public EntityClass(IEntityRepository entityRepository)
{
this._entityRepository = entityRepository;
}
public string Name { get; set; }
public string GetNameWithPrefix(int id)
{
string name = string.Empty;
if (id > 0)
{
name = this._entityRepository.GetName(id);
}
return "Mr. " + name;
}
}
public interface IEntityRepository
{
string GetName(int id);
}
public class EntityRepository:IEntityRepository
{
public string GetName(int id)
{
// Code to connect to DB and get name based on Id
return "NameFromDb";
}
}
}
Ich habe in meiner Antwort Python-Beispiele verwendet, um die Unterschiede zu verdeutlichen.
Stummel - Stubbing ist eine Technik der Softwareentwicklung, die dazu dient, Methoden von Klassen zu einem frühen Zeitpunkt im Entwicklungszyklus zu implementieren. Sie werden üblicherweise als Platzhalter für die Implementierung einer bekannten Schnittstelle verwendet, wobei die Schnittstelle fertiggestellt oder bekannt ist, die Implementierung jedoch noch nicht bekannt oder fertiggestellt ist. Sie beginnen mit Stubs, was einfach bedeutet, dass Sie nur die Definition einer Funktion niederschreiben und den eigentlichen Code für später aufheben. Das hat den Vorteil, dass Sie keine Methoden vergessen und dass Sie weiter über Ihren Entwurf nachdenken können, während Sie ihn im Code sehen. Sie können Ihren Stub auch eine statische Antwort zurückgeben lassen, so dass die Antwort sofort von anderen Teilen Ihres Codes verwendet werden kann. Stub-Objekte liefern eine gültige Antwort, die jedoch statisch ist - egal, welche Eingabe Sie machen, Sie erhalten immer die gleiche Antwort:
class Foo(object):
def bar1(self):
pass
def bar2(self):
#or ...
raise NotImplementedError
def bar3(self):
#or return dummy data
return "Dummy Data"
Mock Objekte werden in Mock-Testfällen verwendet, um zu überprüfen, ob bestimmte Methoden auf diesen Objekten aufgerufen werden. Mock-Objekte sind simulierte Objekte, die das Verhalten von realen Objekten auf kontrollierte Weise imitieren. In der Regel wird ein Mock-Objekt erstellt, um das Verhalten eines anderen Objekts zu testen. Mit Mocks lassen sich Ressourcen simulieren, die für Unit-Tests entweder nicht verfügbar oder zu unhandlich sind.
mymodule.py:
import os
import os.path
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
test.py:
from mymodule import rm
import mock
import unittest
class RmTestCase(unittest.TestCase):
@mock.patch('mymodule.os')
def test_rm(self, mock_os):
rm("any path")
# test that rm called os.remove with the right parameters
mock_os.remove.assert_called_with("any path")
if __name__ == '__main__':
unittest.main()
Dies ist ein sehr einfaches Beispiel, das nur rm ausführt und den Parameter feststellt, mit dem es aufgerufen wurde. Sie können mock mit Objekten verwenden, nicht nur mit Funktionen wie hier gezeigt, und Sie können auch einen Wert zurückgeben, so dass ein mock-Objekt verwendet werden kann, um einen Stub für Tests zu ersetzen.
Mehr dazu unittest.mock Beachten Sie, dass mock in Python 2.x nicht in unittest enthalten ist, sondern ein herunterladbares Modul ist, das über pip heruntergeladen werden kann (pip install mock).
Ich habe auch "The Art of Unit Testing" von Roy Osherove gelesen und ich denke, es wäre toll, wenn ein ähnliches Buch mit Python und Python-Beispielen geschrieben würde. Wenn jemand ein solches Buch kennt, teilen Sie es bitte mit. Prost :)
Ein Stub ist eine leere Funktion, die verwendet wird, um unbehandelte Ausnahmen bei Tests zu vermeiden:
function foo(){}
Ein Mock ist eine künstliche Funktion, die verwendet wird, um Betriebssystem-, Umgebungs- oder Hardware-Abhängigkeiten während der Tests zu vermeiden:
function foo(bar){ window = this; return window.toString(bar); }
In Bezug auf die Behauptungen und den Zustand:
- Mocks werden vor einem Ereignis oder einer Zustandsänderung geltend gemacht
- Stubs werden nicht durchgesetzt, sie bieten einen Zustand vor einem Ereignis, um die Ausführung von Code aus nicht verwandten Einheiten zu vermeiden
- Spione werden wie Stichleitungen eingerichtet und dann nach einem Ereignis oder einer Zustandsänderung aktiviert.
- Fakes werden nicht bestätigt, sie laufen nach einem Ereignis mit fest kodierten Abhängigkeiten, um einen Zustand zu vermeiden
Referenzen
Angenommen, Sie haben eine Klasse namens EmployeeService, die Sie testen möchten und die eine Abhängigkeit von einer Schnittstelle namens EmployeeDao hat:
public class EmployeeService{
private EmployeeDao dao;
public EmployeeService(Dao dao){this.dao = dao;}
public String getEmployeeName(int id){
Employee emp = bar.goToDatabaseAndBringTheEmployeeWithId(id);
return emp != null?emp.getFullName:null;
}
//Further state and behavior
}
public interface EmployeeDao{
Employee goToDatabaseAndBringTheEmployeeWithId(int id);
}
Innerhalb Ihrer Testklasse:
public class EmployeeServiceTest{
EmployeeService service;
EmployeeDao mockDao = Mockito.mock(EmployeeDao.class);//Line 3
@Before
public void setUp(){
service = new EmployeeService(mockDao);
}
//Tests
//....
}
In der obigen Testklasse in Zeile 3 sagen wir zum Mocking-Framework (in diesem Fall Mockito): "Hey, Mockito, bastle mir ein Objekt, das die EmployeeDao-Funktionalität hat." Das Framework wird ein Objekt erstellen, das über die Methode goToDatabaseAndBringTheEmployeeWithId
aber eigentlich ohne Körper. Es ist Ihre Aufgabe, der Attrappe Anweisungen zu geben, was sie tun soll. Dies ist eine Attrappe.
Sie könnten aber auch eine Klasse erstellen, die die EmployeeDao-Schnittstelle implementiert, und sie stattdessen in der Testklasse verwenden:
public EmployeeDaoStub implements EmployeeDao{
public Employee goToDatabaseAndBringTheEmployeeWithId(int id){
//No trip to DB, just returning a dummy Employee object
return new Employee("John","Woo","123 Lincoln str");
}
}
Innerhalb Ihrer Testklasse verwenden Sie dieses Mal einen Stub anstelle eines Mocks:
public class EmployeeServiceTest{
EmployeeService service;
EmployeeDao daoStub = new EmployeeDaoStub();//Line 3
@Before
public void setUp(){
service = new EmployeeService(daoStub);
}
//Tests
//....
}
Um es zusammenzufassen, sind Stubs die Klassen, die Sie erstellen (oder jemand anderes tut), um eine Abhängigkeit zu imitieren, nur um den gewünschten Zustand zu haben. Ja, wie all die anderen Leute sagen, geht es hauptsächlich um einen Zustand, während Mocks typischerweise von einem Mocking-Framework erstellt werden und man keine Ahnung hat, wie dessen Innereien aussehen. Aber bei Stubs wissen Sie, welche Klasse Sie bekommen werden: Es ist die Klasse, die Sie erstellt haben.
Oh, btw, wenn Ihre Abhängigkeit eine Klasse und nicht eine Schnittstelle ist, können Sie einfach diese Klasse erweitern, um Ihren Stub zu erstellen.